diff options
36 files changed, 858 insertions, 381 deletions
@@ -32,6 +32,8 @@ Incompatible changes * Ignore filenames without file extension given to ``Builder.build_specific()`` API directly +* #6230: The anchor of term in glossary directive is changed if it is consisted + by non-ASCII characters Deprecated ---------- @@ -54,11 +56,17 @@ Deprecated * ``sphinx.directives.TabularColumns`` * ``sphinx.directives.TocTree`` * ``sphinx.directives.VersionChange`` +* ``sphinx.domains.std.StandardDomain._resolve_citation_xref()`` +* ``sphinx.domains.std.StandardDomain.note_citations()`` +* ``sphinx.domains.std.StandardDomain.note_citation_refs()`` +* ``sphinx.domains.std.StandardDomain.note_labels()`` * ``sphinx.environment.NoUri`` * ``sphinx.ext.autodoc.importer.MockFinder`` * ``sphinx.ext.autodoc.importer.MockLoader`` * ``sphinx.ext.autodoc.importer.mock()`` * ``sphinx.ext.autosummary.autolink_role()`` +* ``sphinx.transforms.CitationReferences`` +* ``sphinx.transforms.SmartQuotesSkipper`` * ``sphinx.util.docfields.DocFieldTransformer.preprocess_fieldtypes()`` * ``sphinx.util.node.find_source_node()`` * ``sphinx.util.i18n.find_catalog()`` @@ -71,14 +79,27 @@ Features added -------------- * Add a helper class ``sphinx.transforms.post_transforms.SphinxPostTransform`` -* Add a helper method ``SphinxDirective.set_source_info()`` +* Add helper methods + + - ``PythonDomain.note_module()`` + - ``PythonDomain.note_object()`` + - ``SphinxDirective.set_source_info()`` + * #6180: Support ``--keep-going`` with BuildDoc setup command * ``math`` directive now supports ``:class:`` option * todo: ``todo`` directive now supports ``:name:`` option +* #6232: Enable CLI override of Makefile variables +* #6212 autosummary: Add :confval:`autosummary_imported_members` to display + imported members on autosummary Bugs fixed ---------- +* #6230: Inappropriate node_id has been generated by glossary directive if term + is consisted by non-ASCII characters +* #6213: ifconfig: contents after headings are not shown +* commented term in glossary directive is wrongly recognized + Testing -------- @@ -101,6 +122,21 @@ Bugs fixed ---------- * LaTeX: some system labels are not translated +* RemovedInSphinx30Warning is marked as pending +* deprecation warnings are not emitted + + - sphinx.application.CONFIG_FILENAME + - sphinx.builders.htmlhelp + - :confval:`viewcode_import` + +* #6208: C++, properly parse full xrefs that happen to have a short xref as prefix. +* #6220, #6225: napoleon: AttributeError is raised for raised section having + references +* #6245: circular import error on importing SerializingHTMLBuilder +* #6243: LaTeX: 'releasename' setting for latex_elements is ignored +* #6244: html: Search function is broken with 3rd party themes +* #6263: html: HTML5Translator crashed with invalid field node +* #6262: html theme: The style of field lists has changed in bizstyle theme Testing -------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 99abc56eb..beeafab08 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -116,6 +116,26 @@ The following is a list of deprecated interfaces. - 4.0 - ``sphinx.directives.other.VersionChange`` + * - ``sphinx.domains.std.StandardDomain._resolve_citation_xref()`` + - 2.1 + - 4.0 + - ``sphinx.domains.citation.CitationDomain.resolve_xref()`` + + * - ``sphinx.domains.std.StandardDomain.note_citations()`` + - 2.1 + - 4.0 + - ``sphinx.domains.citation.CitationDomain.note_citation()`` + + * - ``sphinx.domains.std.StandardDomain.note_citation_refs()`` + - 2.1 + - 4.0 + - ``sphinx.domains.citation.CitationDomain.note_citation_reference()`` + + * - ``sphinx.domains.std.StandardDomain.note_labels()`` + - 2.1 + - 4.0 + - ``sphinx.domains.std.StandardDomain.process_doc()`` + * - ``sphinx.environment.NoUri`` - 2.1 - 4.0 @@ -141,6 +161,16 @@ The following is a list of deprecated interfaces. - 4.0 - ``sphinx.ext.autosummary.AutoLink`` + * - ``sphinx.transforms.CitationReferences`` + - 2.1 + - 4.0 + - ``sphinx.domains.citation.CitationReferenceTransform`` + + * - ``sphinx.transforms.SmartQuotesSkipper`` + - 2.1 + - 4.0 + - ``sphinx.domains.citation.CitationDefinitionTransform`` + * - ``sphinx.util.docfields.DocFieldTransformer.preprocess_fieldtypes()`` - 2.1 - 4.0 diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst index d1ac0ad32..16a8cea7e 100644 --- a/doc/usage/extensions/autosummary.rst +++ b/doc/usage/extensions/autosummary.rst @@ -149,6 +149,16 @@ also use these config values: :confval:`autodoc_mock_imports` for more details. It defaults to :confval:`autodoc_mock_imports`. + .. versionadded:: 2.0 + +.. confval:: autosummary_imported_members + + A boolean flag indicating whether to document classes and functions imported + in modules. Default is ``False`` + + .. versionadded:: 2.1 + + Customizing templates --------------------- diff --git a/doc/usage/extensions/ifconfig.rst b/doc/usage/extensions/ifconfig.rst index f64ca6c58..2bd9d0e3b 100644 --- a/doc/usage/extensions/ifconfig.rst +++ b/doc/usage/extensions/ifconfig.rst @@ -8,6 +8,11 @@ This extension is quite simple, and features only one directive: +.. warning:: + + This directive is designed to control only content of document. It could + not control sections, labels and so on. + .. rst:directive:: ifconfig Include content of the directive only if the Python expression given as an diff --git a/doc/usage/extensions/math.rst b/doc/usage/extensions/math.rst index 9e62c1425..e6ccc2941 100644 --- a/doc/usage/extensions/math.rst +++ b/doc/usage/extensions/math.rst @@ -15,7 +15,8 @@ Math support for HTML outputs in Sphinx So mathbase extension is no longer needed. Since mathematical notation isn't natively supported by HTML in any way, Sphinx -gives a math support to HTML document with several extensions. +gives a math support to HTML document with several extensions. These use the +reStructuredText math :rst:dir:`directive <math>` and :rst:role:`role <math>`. :mod:`sphinx.ext.imgmath` -- Render math as images -------------------------------------------------- @@ -131,7 +132,13 @@ MathJax_ is then loaded and transforms the LaTeX markup to readable math live in the browser. Because MathJax (and the necessary fonts) is very large, it is not included in -Sphinx. +Sphinx but is set to automatically include it from a third-party site. + +.. attention:: + + You should use the math :rst:dir:`directive <math>` and + :rst:role:`role <math>`, not the native MathJax ``$$``, ``\(``, etc. + .. confval:: mathjax_path @@ -140,8 +147,9 @@ Sphinx. The default is the ``https://`` URL that loads the JS files from the `cdnjs`__ Content Delivery Network. See the `MathJax Getting Started - page`__ for details. If you want MathJax to be available offline, you have - to download it and set this value to a different path. + page`__ for details. If you want MathJax to be available offline or + without including resources from a third-party site, you have to + download it and set this value to a different path. __ https://cdnjs.com diff --git a/sphinx/application.py b/sphinx/application.py index 516b7be58..afcdc02ed 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -23,7 +23,6 @@ from docutils.parsers.rst import Directive, roles import sphinx from sphinx import package_dir, locale from sphinx.config import Config -from sphinx.config import CONFIG_FILENAME # NOQA # for compatibility (RemovedInSphinx30) from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.environment import BuildEnvironment from sphinx.errors import ApplicationError, ConfigError, VersionRequirementError @@ -73,6 +72,7 @@ builtin_extensions = ( 'sphinx.config', 'sphinx.domains.c', 'sphinx.domains.changeset', + 'sphinx.domains.citation', 'sphinx.domains.cpp', 'sphinx.domains.javascript', 'sphinx.domains.math', diff --git a/sphinx/builders/dirhtml.py b/sphinx/builders/dirhtml.py index 7ab6fad98..d5d61c273 100644 --- a/sphinx/builders/dirhtml.py +++ b/sphinx/builders/dirhtml.py @@ -11,6 +11,7 @@ from os import path from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.util import logging from sphinx.util.osutil import SEP, os_path @@ -55,6 +56,14 @@ class DirectoryHTMLBuilder(StandaloneHTMLBuilder): self.globalcontext['no_search_suffix'] = True +# for compatibility +deprecated_alias('sphinx.builders.html', + { + 'DirectoryHTMLBuilder': DirectoryHTMLBuilder, + }, + RemovedInSphinx40Warning) + + def setup(app): # type: (Sphinx) -> Dict[str, Any] app.setup_extension('sphinx.builders.html') diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 5621f9a75..97774c668 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -24,7 +24,7 @@ from docutils.utils import relative_path from sphinx import package_dir, __display_version__ from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.environment.adapters.asset import ImageAdapter from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.environment.adapters.toctree import TocTree @@ -1187,23 +1187,9 @@ def validate_math_renderer(app): # for compatibility -from sphinx.builders.dirhtml import DirectoryHTMLBuilder # NOQA -from sphinx.builders.singlehtml import SingleFileHTMLBuilder # NOQA -from sphinxcontrib.serializinghtml import ( # NOQA - LAST_BUILD_FILENAME, JSONHTMLBuilder, PickleHTMLBuilder, SerializingHTMLBuilder -) - -deprecated_alias('sphinx.builders.html', - { - 'LAST_BUILD_FILENAME': LAST_BUILD_FILENAME, - 'DirectoryHTMLBuilder': DirectoryHTMLBuilder, - 'JSONHTMLBuilder': JSONHTMLBuilder, - 'PickleHTMLBuilder': PickleHTMLBuilder, - 'SerializingHTMLBuilder': SerializingHTMLBuilder, - 'SingleFileHTMLBuilder': SingleFileHTMLBuilder, - 'WebHTMLBuilder': PickleHTMLBuilder, - }, - RemovedInSphinx40Warning) +import sphinx.builders.dirhtml # NOQA +import sphinx.builders.singlehtml # NOQA +import sphinxcontrib.serializinghtml # NOQA def setup(app): diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py index 2e7e8f083..be365ef7e 100644 --- a/sphinx/builders/htmlhelp.py +++ b/sphinx/builders/htmlhelp.py @@ -24,7 +24,7 @@ if False: from sphinx.application import Sphinx # NOQA -deprecated_alias('sphinx.builders.devhelp', +deprecated_alias('sphinx.builders.htmlhelp', { 'chm_locales': chm_locales, 'chm_htmlescape': chm_htmlescape, diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index e6467601d..973e7c67d 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -208,7 +208,7 @@ class LaTeXBuilder(Builder): self.context['indexname'] = _('Index') if self.config.release: # Show the release label only if release value exists - self.context['releasename'] = _('Release') + self.context.setdefault('releasename', _('Release')) def init_babel(self): # type: () -> None diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py index 746446fbc..6381780ae 100644 --- a/sphinx/builders/latex/transforms.py +++ b/sphinx/builders/latex/transforms.py @@ -16,6 +16,7 @@ from sphinx import addnodes from sphinx.builders.latex.nodes import ( captioned_literal_block, footnotemark, footnotetext, math_reference, thebibliography ) +from sphinx.domains.citation import CitationDomain from sphinx.transforms import SphinxTransform from sphinx.transforms.post_transforms import SphinxPostTransform from sphinx.util.nodes import NodeMatcher @@ -545,10 +546,10 @@ class CitationReferenceTransform(SphinxPostTransform): def run(self, **kwargs): # type: (Any) -> None - matcher = NodeMatcher(addnodes.pending_xref, refdomain='std', reftype='citation') - citations = self.env.get_domain('std').data['citations'] + domain = cast(CitationDomain, self.env.get_domain('citation')) + matcher = NodeMatcher(addnodes.pending_xref, refdomain='citation', reftype='ref') for node in self.document.traverse(matcher): # type: addnodes.pending_xref - docname, labelid, _ = citations.get(node['reftarget'], ('', '', 0)) + docname, labelid, _ = domain.citations.get(node['reftarget'], ('', '', 0)) if docname: citation_ref = nodes.citation_reference('', '', *node.children, docname=docname, refname=labelid) diff --git a/sphinx/builders/singlehtml.py b/sphinx/builders/singlehtml.py index 1ee5a37b1..068d1c1c2 100644 --- a/sphinx/builders/singlehtml.py +++ b/sphinx/builders/singlehtml.py @@ -13,6 +13,7 @@ from os import path from docutils import nodes from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.environment.adapters.toctree import TocTree from sphinx.locale import __ from sphinx.util import logging @@ -201,6 +202,14 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn) +# for compatibility +deprecated_alias('sphinx.builders.html', + { + 'SingleFileHTMLBuilder': SingleFileHTMLBuilder, + }, + RemovedInSphinx40Warning) + + def setup(app): # type: (Sphinx) -> Dict[str, Any] app.setup_extension('sphinx.builders.html') diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index fd7bec586..40f838c48 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -166,7 +166,7 @@ class ObjectDescription(SphinxDirective): node['objtype'] = node['desctype'] = self.objtype node['noindex'] = noindex = ('noindex' in self.options) - self.names = [] # type: List[str] + self.names = [] # type: List[Any] signatures = self.get_signatures() for i, sig in enumerate(signatures): # add a signature node for each signature in the current unit diff --git a/sphinx/domains/citation.py b/sphinx/domains/citation.py new file mode 100644 index 000000000..2bb49def9 --- /dev/null +++ b/sphinx/domains/citation.py @@ -0,0 +1,167 @@ +""" + sphinx.domains.citation + ~~~~~~~~~~~~~~~~~~~~~~~ + + The citation domain. + + :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from typing import cast + +from docutils import nodes + +from sphinx import addnodes +from sphinx.domains import Domain +from sphinx.locale import __ +from sphinx.transforms import SphinxTransform +from sphinx.util import logging +from sphinx.util.nodes import copy_source_info, make_refnode + +if False: + # For type annotation + from typing import Any, Dict, List, Set, Tuple, Union # NOQA + from sphinx.application import Sphinx # NOQA + from sphinx.builders import Builder # NOQA + from sphinx.environment import BuildEnvironment # NOQA + +logger = logging.getLogger(__name__) + + +class CitationDomain(Domain): + """Domain for citations.""" + + name = 'citation' + label = 'citation' + + dangling_warnings = { + 'ref': 'citation not found: %(target)s', + } + + @property + def citations(self): + # type: () -> Dict[str, Tuple[str, str, int]] + return self.data.setdefault('citations', {}) + + @property + def citation_refs(self): + # type: () -> Dict[str, Set[str]] + return self.data.setdefault('citation_refs', {}) + + def clear_doc(self, docname): + # type: (str) -> None + for key, (fn, _l, lineno) in list(self.citations.items()): + if fn == docname: + del self.citations[key] + for key, docnames in list(self.citation_refs.items()): + if docnames == {docname}: + del self.citation_refs[key] + elif docname in docnames: + docnames.remove(docname) + + def merge_domaindata(self, docnames, otherdata): + # type: (List[str], Dict) -> None + # XXX duplicates? + for key, data in otherdata['citations'].items(): + if data[0] in docnames: + self.citations[key] = data + for key, data in otherdata['citation_refs'].items(): + citation_refs = self.citation_refs.setdefault(key, set()) + for docname in data: + if docname in docnames: + citation_refs.add(docname) + + def note_citation(self, node): + # type: (nodes.citation) -> None + label = node[0].astext() + if label in self.citations: + path = self.env.doc2path(self.citations[label][0]) + logger.warning(__('duplicate citation %s, other instance in %s'), label, path, + location=node, type='ref', subtype='citation') + self.citations[label] = (node['docname'], node['ids'][0], node.line) + + def note_citation_reference(self, node): + # type: (addnodes.pending_xref) -> None + docnames = self.citation_refs.setdefault(node['reftarget'], set()) + docnames.add(self.env.docname) + + def check_consistency(self): + # type: () -> None + for name, (docname, labelid, lineno) in self.citations.items(): + if name not in self.citation_refs: + logger.warning(__('Citation [%s] is not referenced.'), name, + type='ref', subtype='citation', location=(docname, lineno)) + + def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): + # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + docname, labelid, lineno = self.citations.get(target, ('', '', 0)) + if not docname: + return None + + return make_refnode(builder, fromdocname, docname, + labelid, contnode) + + def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): + # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA + refnode = self.resolve_xref(env, fromdocname, builder, 'ref', target, node, contnode) + if refnode is None: + return [] + else: + return [('ref', refnode)] + + +class CitationDefinitionTransform(SphinxTransform): + """Mark citation definition labels as not smartquoted.""" + default_priority = 619 + + def apply(self, **kwargs): + # type: (Any) -> None + domain = cast(CitationDomain, self.env.get_domain('citation')) + for node in self.document.traverse(nodes.citation): + # register citation node to domain + node['docname'] = self.env.docname + domain.note_citation(node) + + # mark citation labels as not smartquoted + label = cast(nodes.label, node[0]) + label['support_smartquotes'] = False + + +class CitationReferenceTransform(SphinxTransform): + """ + Replace citation references by pending_xref nodes before the default + docutils transform tries to resolve them. + """ + default_priority = 619 + + def apply(self, **kwargs): + # type: (Any) -> None + domain = cast(CitationDomain, self.env.get_domain('citation')) + for node in self.document.traverse(nodes.citation_reference): + target = node.astext() + ref = addnodes.pending_xref(target, refdomain='citation', reftype='ref', + reftarget=target, refwarn=True, + support_smartquotes=False, + ids=node["ids"], + classes=node.get('classes', [])) + ref += nodes.inline(target, '[%s]' % target) + copy_source_info(node, ref) + node.replace_self(ref) + + # register reference node to domain + domain.note_citation_reference(ref) + + +def setup(app): + # type: (Sphinx) -> Dict[str, Any] + app.add_domain(CitationDomain) + app.add_transform(CitationDefinitionTransform) + app.add_transform(CitationReferenceTransform) + + return { + 'version': 'builtin', + 'env_version': 1, + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 6760328d4..a9f4b1c24 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -6391,6 +6391,7 @@ class DefinitionParser: # if there are '()' left, just skip them self.skip_ws() self.skip_string('()') + self.assert_end() templatePrefix = self._check_template_consistency(name, templatePrefix, fullSpecShorthand=True) res1 = ASTNamespace(name, templatePrefix) @@ -6403,6 +6404,7 @@ class DefinitionParser: # if there are '()' left, just skip them self.skip_ws() self.skip_string('()') + self.assert_end() return res2, False except DefinitionError as e2: errs = [] @@ -7145,7 +7147,6 @@ class CPPDomain(Domain): parser = DefinitionParser(target, warner, env.config) try: ast, isShorthand = parser.parse_xref_object() - parser.assert_end() except DefinitionError as e: def findWarning(e): # as arg to stop flake8 from complaining if typ != 'any' and typ != 'func': @@ -7154,7 +7155,6 @@ class CPPDomain(Domain): parser2 = DefinitionParser(target[:-2], warner, env.config) try: parser2.parse_xref_object() - parser2.assert_end() except DefinitionError as e2: return target[:-2], e2 # strange, that we don't get the error now, use the original diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 2203ee6e3..1e334959f 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -9,6 +9,7 @@ """ import re +from typing import cast from docutils import nodes from docutils.parsers.rst import directives @@ -309,14 +310,13 @@ class PyObject(ObjectDescription): return fullname, prefix def get_index_text(self, modname, name): - # type: (str, str) -> str + # type: (str, Tuple[str, str]) -> str """Return the text for the index entry of the object.""" raise NotImplementedError('must be implemented in subclasses') def add_target_and_index(self, name_cls, sig, signode): - # type: (str, str, addnodes.desc_signature) -> None - modname = self.options.get( - 'module', self.env.ref_context.get('py:module')) + # type: (Tuple[str, str], str, addnodes.desc_signature) -> None + modname = self.options.get('module', self.env.ref_context.get('py:module')) fullname = (modname and modname + '.' or '') + name_cls[0] # note target if fullname not in self.state.document.ids: @@ -324,15 +324,9 @@ class PyObject(ObjectDescription): signode['ids'].append(fullname) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) - objects = self.env.domaindata['py']['objects'] - if fullname in objects: - self.state_machine.reporter.warning( - 'duplicate object description of %s, ' % fullname + - 'other instance in ' + - self.env.doc2path(objects[fullname][0]) + - ', use :noindex: for one of them', - line=self.lineno) - objects[fullname] = (self.env.docname, self.objtype) + + domain = cast(PythonDomain, self.env.get_domain('py')) + domain.note_object(fullname, self.objtype) indextext = self.get_index_text(modname, name_cls) if indextext: @@ -410,7 +404,7 @@ class PyModulelevel(PyObject): return self.objtype == 'function' def get_index_text(self, modname, name_cls): - # type: (str, str) -> str + # type: (str, Tuple[str, str]) -> str if self.objtype == 'function': if not modname: return _('%s() (built-in function)') % name_cls[0] @@ -435,7 +429,7 @@ class PyClasslike(PyObject): return self.objtype + ' ' def get_index_text(self, modname, name_cls): - # type: (str, str) -> str + # type: (str, Tuple[str, str]) -> str if self.objtype == 'class': if not modname: return _('%s (built-in class)') % name_cls[0] @@ -464,7 +458,7 @@ class PyClassmember(PyObject): return '' def get_index_text(self, modname, name_cls): - # type: (str, str) -> str + # type: (str, Tuple[str, str]) -> str name, cls = name_cls add_modules = self.env.config.add_module_names if self.objtype == 'method': @@ -575,18 +569,20 @@ class PyModule(SphinxDirective): def run(self): # type: () -> List[nodes.Node] + domain = cast(PythonDomain, self.env.get_domain('py')) + modname = self.arguments[0].strip() noindex = 'noindex' in self.options self.env.ref_context['py:module'] = modname ret = [] # type: List[nodes.Node] if not noindex: - self.env.domaindata['py']['modules'][modname] = (self.env.docname, - self.options.get('synopsis', ''), - self.options.get('platform', ''), - 'deprecated' in self.options) - # make a duplicate entry in 'objects' to facilitate searching for - # the module in PythonDomain.find_obj() - self.env.domaindata['py']['objects'][modname] = (self.env.docname, 'module') + # note module to the domain + domain.note_module(modname, + self.options.get('synopsis', ''), + self.options.get('platform', ''), + 'deprecated' in self.options) + domain.note_object(modname, 'module') + targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True) self.state.document.note_explicit_target(targetnode) @@ -769,24 +765,55 @@ class PythonDomain(Domain): PythonModuleIndex, ] + @property + def objects(self): + # type: () -> Dict[str, Tuple[str, str]] + return self.data.setdefault('objects', {}) # fullname -> docname, objtype + + def note_object(self, name, objtype, location=None): + # type: (str, str, Any) -> None + """Note a python object for cross reference. + + .. versionadded:: 2.1 + """ + if name in self.objects: + docname = self.objects[name][0] + logger.warning(__('duplicate object description of %s, ' + 'other instance in %s, use :noindex: for one of them'), + name, docname, location=location) + self.objects[name] = (self.env.docname, objtype) + + @property + def modules(self): + # type: () -> Dict[str, Tuple[str, str, str, bool]] + return self.data.setdefault('modules', {}) # modname -> docname, synopsis, platform, deprecated # NOQA + + def note_module(self, name, synopsis, platform, deprecated): + # type: (str, str, str, bool) -> None + """Note a python module for cross reference. + + .. versionadded:: 2.1 + """ + self.modules[name] = (self.env.docname, synopsis, platform, deprecated) + def clear_doc(self, docname): # type: (str) -> None - for fullname, (fn, _l) in list(self.data['objects'].items()): + for fullname, (fn, _l) in list(self.objects.items()): if fn == docname: - del self.data['objects'][fullname] - for modname, (fn, _x, _x, _x) in list(self.data['modules'].items()): + del self.objects[fullname] + for modname, (fn, _x, _x, _y) in list(self.modules.items()): if fn == docname: - del self.data['modules'][modname] + del self.modules[modname] def merge_domaindata(self, docnames, otherdata): # type: (List[str], Dict) -> None # XXX check duplicates? for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: - self.data['objects'][fullname] = (fn, objtype) + self.objects[fullname] = (fn, objtype) for modname, data in otherdata['modules'].items(): if data[0] in docnames: - self.data['modules'][modname] = data + self.modules[modname] = data def find_obj(self, env, modname, classname, name, type, searchmode=0): # type: (BuildEnvironment, str, str, str, str, int) -> List[Tuple[str, Any]] @@ -800,7 +827,6 @@ class PythonDomain(Domain): if not name: return [] - objects = self.data['objects'] matches = [] # type: List[Tuple[str, Any]] newname = None @@ -812,44 +838,44 @@ class PythonDomain(Domain): if objtypes is not None: if modname and classname: fullname = modname + '.' + classname + '.' + name - if fullname in objects and objects[fullname][1] in objtypes: + if fullname in self.objects and self.objects[fullname][1] in objtypes: newname = fullname if not newname: - if modname and modname + '.' + name in objects and \ - objects[modname + '.' + name][1] in objtypes: + if modname and modname + '.' + name in self.objects and \ + self.objects[modname + '.' + name][1] in objtypes: newname = modname + '.' + name - elif name in objects and objects[name][1] in objtypes: + elif name in self.objects and self.objects[name][1] in objtypes: newname = name else: # "fuzzy" searching mode searchname = '.' + name - matches = [(oname, objects[oname]) for oname in objects + matches = [(oname, self.objects[oname]) for oname in self.objects if oname.endswith(searchname) and - objects[oname][1] in objtypes] + self.objects[oname][1] in objtypes] else: # NOTE: searching for exact match, object type is not considered - if name in objects: + if name in self.objects: newname = name elif type == 'mod': # only exact matches allowed for modules return [] - elif classname and classname + '.' + name in objects: + elif classname and classname + '.' + name in self.objects: newname = classname + '.' + name - elif modname and modname + '.' + name in objects: + elif modname and modname + '.' + name in self.objects: newname = modname + '.' + name elif modname and classname and \ - modname + '.' + classname + '.' + name in objects: + modname + '.' + classname + '.' + name in self.objects: newname = modname + '.' + classname + '.' + name # special case: builtin exceptions have module "exceptions" set elif type == 'exc' and '.' not in name and \ - 'exceptions.' + name in objects: + 'exceptions.' + name in self.objects: newname = 'exceptions.' + name # special case: object methods elif type in ('func', 'meth') and '.' not in name and \ - 'object.' + name in objects: + 'object.' + name in self.objects: newname = 'object.' + name if newname is not None: - matches.append((newname, objects[newname])) + matches.append((newname, self.objects[newname])) return matches def resolve_xref(self, env, fromdocname, builder, @@ -896,7 +922,7 @@ class PythonDomain(Domain): def _make_module_refnode(self, builder, fromdocname, name, contnode): # type: (Builder, str, str, nodes.Node) -> nodes.Element # get additional info for modules - docname, synopsis, platform, deprecated = self.data['modules'][name] + docname, synopsis, platform, deprecated = self.modules[name] title = name if synopsis: title += ': ' + synopsis @@ -909,9 +935,9 @@ class PythonDomain(Domain): def get_objects(self): # type: () -> Iterator[Tuple[str, str, str, str, str, int]] - for modname, info in self.data['modules'].items(): + for modname, info in self.modules.items(): yield (modname, modname, 'module', info[0], 'module-' + modname, 0) - for refname, (docname, type) in self.data['objects'].items(): + for refname, (docname, type) in self.objects.items(): if type != 'module': # modules are already handled yield (refname, refname, type, docname, refname, 1) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index e73c660e6..6574c9b76 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -18,6 +18,7 @@ from docutils.parsers.rst import directives from docutils.statemachine import StringList from sphinx import addnodes +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.errors import NoUri @@ -255,6 +256,9 @@ def make_glossary_term(env, textnodes, index_key, source, lineno, new_id=None): termtext = term.astext() if new_id is None: new_id = nodes.make_id('term-' + termtext) + if new_id == 'term': + # the term is not good for node_id. Generate it by sequence number instead. + new_id = 'term-' + str(len(gloss_entries)) if new_id in gloss_entries: new_id = 'term-' + str(len(gloss_entries)) gloss_entries.add(new_id) @@ -300,6 +304,7 @@ class Glossary(SphinxDirective): # first, collect single entries entries = [] # type: List[Tuple[List[Tuple[str, str, int]], StringList]] in_definition = True + in_comment = False was_empty = True messages = [] # type: List[nodes.Node] for line, (source, lineno) in zip(self.content, self.content.items): @@ -313,27 +318,33 @@ class Glossary(SphinxDirective): if line and not line[0].isspace(): # enable comments if line.startswith('.. '): + in_comment = True continue + else: + in_comment = False + # first term of definition if in_definition: if not was_empty: - messages.append(self.state.reporter.system_message( - 2, 'glossary term must be preceded by empty line', + messages.append(self.state.reporter.warning( + _('glossary term must be preceded by empty line'), source=source, line=lineno)) entries.append(([(line, source, lineno)], StringList())) in_definition = False # second term and following else: if was_empty: - messages.append(self.state.reporter.system_message( - 2, 'glossary terms must not be separated by empty ' - 'lines', source=source, line=lineno)) + messages.append(self.state.reporter.warning( + _('glossary terms must not be separated by empty lines'), + source=source, line=lineno)) if entries: entries[-1][0].append((line, source, lineno)) else: - messages.append(self.state.reporter.system_message( - 2, 'glossary seems to be misformatted, check ' - 'indentation', source=source, line=lineno)) + messages.append(self.state.reporter.warning( + _('glossary seems to be misformatted, check indentation'), + source=source, line=lineno)) + elif in_comment: + pass else: if not in_definition: # first line of definition, determines indentation @@ -342,9 +353,9 @@ class Glossary(SphinxDirective): if entries: entries[-1][1].append(line[indent_len:], source, lineno) else: - messages.append(self.state.reporter.system_message( - 2, 'glossary seems to be misformatted, check ' - 'indentation', source=source, line=lineno)) + messages.append(self.state.reporter.warning( + _('glossary seems to be misformatted, check indentation'), + source=source, line=lineno)) was_empty = False # now, parse all the entries into a big definition list @@ -494,8 +505,6 @@ class StandardDomain(Domain): initial_data = { 'progoptions': {}, # (program, name) -> docname, labelid 'objects': {}, # (type, name) -> docname, labelid - 'citations': {}, # citation_name -> docname, labelid, lineno - 'citation_refs': {}, # citation_name -> list of docnames 'labels': { # labelname -> docname, labelid, sectionname 'genindex': ('genindex', '', _('Index')), 'modindex': ('py-modindex', '', _('Module Index')), @@ -516,7 +525,6 @@ class StandardDomain(Domain): 'keyword': 'unknown keyword: %(target)s', 'doc': 'unknown document: %(target)s', 'option': 'unknown option: %(target)s', - 'citation': 'citation not found: %(target)s', } enumerable_nodes = { # node_class -> (figtype, title_getter) @@ -534,81 +542,60 @@ class StandardDomain(Domain): for node, settings in env.app.registry.enumerable_nodes.items(): self.enumerable_nodes[node] = settings + @property + def objects(self): + # type: () -> Dict[Tuple[str, str], Tuple[str, str]] + return self.data.setdefault('objects', {}) # (objtype, name) -> docname, labelid + + @property + def progoptions(self): + # type: () -> Dict[Tuple[str, str], Tuple[str, str]] + return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid + + @property + def labels(self): + # type: () -> Dict[str, Tuple[str, str, str]] + return self.data.setdefault('labels', {}) # labelname -> docname, labelid, sectionname + + @property + def anonlabels(self): + # type: () -> Dict[str, Tuple[str, str]] + return self.data.setdefault('anonlabels', {}) # labelname -> docname, labelid + def clear_doc(self, docname): # type: (str) -> None - for key, (fn, _l) in list(self.data['progoptions'].items()): - if fn == docname: - del self.data['progoptions'][key] - for key, (fn, _l) in list(self.data['objects'].items()): + key = None # type: Any + for key, (fn, _l) in list(self.progoptions.items()): if fn == docname: - del self.data['objects'][key] - for key, (fn, _l, lineno) in list(self.data['citations'].items()): + del self.progoptions[key] + for key, (fn, _l) in list(self.objects.items()): if fn == docname: - del self.data['citations'][key] - for key, docnames in list(self.data['citation_refs'].items()): - if docnames == [docname]: - del self.data['citation_refs'][key] - elif docname in docnames: - docnames.remove(docname) - for key, (fn, _l, _l) in list(self.data['labels'].items()): + del self.objects[key] + for key, (fn, _l, _l) in list(self.labels.items()): if fn == docname: - del self.data['labels'][key] - for key, (fn, _l) in list(self.data['anonlabels'].items()): + del self.labels[key] + for key, (fn, _l) in list(self.anonlabels.items()): if fn == docname: - del self.data['anonlabels'][key] + del self.anonlabels[key] def merge_domaindata(self, docnames, otherdata): # type: (List[str], Dict) -> None # XXX duplicates? for key, data in otherdata['progoptions'].items(): if data[0] in docnames: - self.data['progoptions'][key] = data + self.progoptions[key] = data for key, data in otherdata['objects'].items(): if data[0] in docnames: - self.data['objects'][key] = data - for key, data in otherdata['citations'].items(): - if data[0] in docnames: - self.data['citations'][key] = data - for key, data in otherdata['citation_refs'].items(): - citation_refs = self.data['citation_refs'].setdefault(key, []) - for docname in data: - if docname in docnames: - citation_refs.append(docname) + self.objects[key] = data for key, data in otherdata['labels'].items(): if data[0] in docnames: - self.data['labels'][key] = data + self.labels[key] = data for key, data in otherdata['anonlabels'].items(): if data[0] in docnames: - self.data['anonlabels'][key] = data + self.anonlabels[key] = data def process_doc(self, env, docname, document): # type: (BuildEnvironment, str, nodes.document) -> None - self.note_citations(env, docname, document) - self.note_citation_refs(env, docname, document) - self.note_labels(env, docname, document) - - def note_citations(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None - for node in document.traverse(nodes.citation): - node['docname'] = docname - label = cast(nodes.label, node[0]).astext() - if label in self.data['citations']: - path = env.doc2path(self.data['citations'][label][0]) - logger.warning(__('duplicate citation %s, other instance in %s'), label, path, - location=node, type='ref', subtype='citation') - self.data['citations'][label] = (docname, node['ids'][0], node.line) - - def note_citation_refs(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None - for node in document.traverse(addnodes.pending_xref): - if node['refdomain'] == 'std' and node['reftype'] == 'citation': - label = node['reftarget'] - citation_refs = self.data['citation_refs'].setdefault(label, []) - citation_refs.append(docname) - - def note_labels(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None - labels, anonlabels = self.data['labels'], self.data['anonlabels'] for name, explicit in document.nametypes.items(): if not explicit: continue @@ -626,11 +613,11 @@ class StandardDomain(Domain): # ignore footnote labels, labels automatically generated from a # link and object descriptions continue - if name in labels: + if name in self.labels: logger.warning(__('duplicate label %s, other instance in %s'), - name, env.doc2path(labels[name][0]), + name, env.doc2path(self.labels[name][0]), location=node) - anonlabels[name] = docname, labelid + self.anonlabels[name] = docname, labelid if node.tagname in ('section', 'rubric'): title = cast(nodes.title, node[0]) sectname = clean_astext(title) @@ -647,23 +634,15 @@ class StandardDomain(Domain): else: # anonymous-only labels continue - labels[name] = docname, labelid, sectname + self.labels[name] = docname, labelid, sectname def add_object(self, objtype, name, docname, labelid): # type: (str, str, str, str) -> None - self.data['objects'][objtype, name] = (docname, labelid) + self.objects[objtype, name] = (docname, labelid) def add_program_option(self, program, name, docname, labelid): # type: (str, str, str, str) -> None - self.data['progoptions'][program, name] = (docname, labelid) - - def check_consistency(self): - # type: () -> None - for name, (docname, labelid, lineno) in self.data['citations'].items(): - if name not in self.data['citation_refs']: - logger.warning(__('Citation [%s] is not referenced.'), name, - type='ref', subtype='citation', - location=(docname, lineno)) + self.progoptions[program, name] = (docname, labelid) def build_reference_node(self, fromdocname, builder, docname, labelid, sectname, rolename, **options): @@ -703,7 +682,10 @@ class StandardDomain(Domain): elif typ == 'option': resolver = self._resolve_option_xref elif typ == 'citation': - resolver = self._resolve_citation_xref + warnings.warn('pending_xref(domain=std, type=citation) is deprecated: %r' % node, + RemovedInSphinx40Warning) + domain = env.get_domain('citation') + return domain.resolve_xref(env, fromdocname, builder, typ, target, node, contnode) else: resolver = self._resolve_obj_xref @@ -714,13 +696,12 @@ class StandardDomain(Domain): if node['refexplicit']: # reference to anonymous label; the reference uses # the supplied link caption - docname, labelid = self.data['anonlabels'].get(target, ('', '')) + docname, labelid = self.anonlabels.get(target, ('', '')) sectname = node.astext() else: # reference to named label; the final node will # contain the section name after the label - docname, labelid, sectname = self.data['labels'].get(target, - ('', '', '')) + docname, labelid, sectname = self.labels.get(target, ('', '', '')) if not docname: return None @@ -729,10 +710,10 @@ class StandardDomain(Domain): def _resolve_numref_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA - if target in self.data['labels']: - docname, labelid, figname = self.data['labels'].get(target, ('', '', '')) + if target in self.labels: + docname, labelid, figname = self.labels.get(target, ('', '', '')) else: - docname, labelid = self.data['anonlabels'].get(target, ('', '')) + docname, labelid = self.anonlabels.get(target, ('', '')) figname = None if not docname: @@ -791,7 +772,7 @@ class StandardDomain(Domain): def _resolve_keyword_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA # keywords are oddballs: they are referenced by named labels - docname, labelid, _ = self.data['labels'].get(target, ('', '', '')) + docname, labelid, _ = self.labels.get(target, ('', '', '')) if not docname: return None return make_refnode(builder, fromdocname, docname, @@ -817,7 +798,7 @@ class StandardDomain(Domain): # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA progname = node.get('std:program') target = target.strip() - docname, labelid = self.data['progoptions'].get((progname, target), ('', '')) + docname, labelid = self.progoptions.get((progname, target), ('', '')) if not docname: commands = [] while ws_re.search(target): @@ -825,8 +806,7 @@ class StandardDomain(Domain): commands.append(subcommand) progname = "-".join(commands) - docname, labelid = self.data['progoptions'].get((progname, target), - ('', '')) + docname, labelid = self.progoptions.get((progname, target), ('', '')) if docname: break else: @@ -835,33 +815,12 @@ class StandardDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) - def _resolve_citation_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA - docname, labelid, lineno = self.data['citations'].get(target, ('', '', 0)) - if not docname: - if 'ids' in node: - # remove ids attribute that annotated at - # transforms.CitationReference.apply. - del node['ids'][:] - return None - - try: - return make_refnode(builder, fromdocname, docname, - labelid, contnode) - except NoUri: - # remove the ids we added in the CitationReferences - # transform since they can't be transfered to - # the contnode (if it's a Text node) - if not isinstance(contnode, nodes.Element): - del node['ids'][:] - raise - def _resolve_obj_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA objtypes = self.objtypes_for_role(typ) or [] for objtype in objtypes: - if (objtype, target) in self.data['objects']: - docname, labelid = self.data['objects'][objtype, target] + if (objtype, target) in self.objects: + docname, labelid = self.objects[objtype, target] break else: docname, labelid = '', '' @@ -885,8 +844,8 @@ class StandardDomain(Domain): key = (objtype, target) if objtype == 'term': key = (objtype, ltarget) - if key in self.data['objects']: - docname, labelid = self.data['objects'][key] + if key in self.objects: + docname, labelid = self.objects[key] results.append(('std:' + self.role_for_objtype(objtype), make_refnode(builder, fromdocname, docname, labelid, contnode))) @@ -897,22 +856,22 @@ class StandardDomain(Domain): # handle the special 'doc' reference here for doc in self.env.all_docs: yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1) - for (prog, option), info in self.data['progoptions'].items(): + for (prog, option), info in self.progoptions.items(): if prog: fullname = ".".join([prog, option]) yield (fullname, fullname, 'cmdoption', info[0], info[1], 1) else: yield (option, option, 'cmdoption', info[0], info[1], 1) - for (type, name), info in self.data['objects'].items(): + for (type, name), info in self.objects.items(): yield (name, name, type, info[0], info[1], self.object_types[type].attrs['searchprio']) - for name, info in self.data['labels'].items(): - yield (name, info[2], 'label', info[0], info[1], -1) + for name, (docname, labelid, sectionname) in self.labels.items(): + yield (name, sectionname, 'label', docname, labelid, -1) # add anonymous-only labels as well - non_anon_labels = set(self.data['labels']) - for name, info in self.data['anonlabels'].items(): + non_anon_labels = set(self.labels) + for name, (docname, labelid) in self.anonlabels.items(): if name not in non_anon_labels: - yield (name, name, 'label', info[0], info[1], -1) + yield (name, name, 'label', docname, labelid, -1) def get_type_name(self, type, primary=False): # type: (ObjType, bool) -> str @@ -993,6 +952,21 @@ class StandardDomain(Domain): else: return None + def note_citations(self, env, docname, document): + # type: (BuildEnvironment, str, nodes.document) -> None + warnings.warn('StandardDomain.note_citations() is deprecated.', + RemovedInSphinx40Warning) + + def note_citation_refs(self, env, docname, document): + # type: (BuildEnvironment, str, nodes.document) -> None + warnings.warn('StandardDomain.note_citation_refs() is deprecated.', + RemovedInSphinx40Warning) + + def note_labels(self, env, docname, document): + # type: (BuildEnvironment, str, nodes.document) -> None + warnings.warn('StandardDomain.note_labels() is deprecated.', + RemovedInSphinx40Warning) + def setup(app): # type: (Sphinx) -> Dict[str, Any] diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index b201a8a56..952bd9e2a 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -733,11 +733,12 @@ def process_generate_options(app): 'But your source_suffix does not contain .rst. Skipped.')) return + imported_members = app.config.autosummary_imported_members with mock(app.config.autosummary_mock_imports): generate_autosummary_docs(genfiles, builder=app.builder, warn=logger.warning, info=logger.info, suffix=suffix, base_path=app.srcdir, - app=app) + app=app, imported_members=imported_members) def setup(app): @@ -763,5 +764,6 @@ def setup(app): app.add_config_value('autosummary_generate', [], True, [bool]) app.add_config_value('autosummary_mock_imports', lambda config: config.autodoc_mock_imports, 'env') + app.add_config_value('autosummary_imported_members', [], False, [bool]) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/ifconfig.py b/sphinx/ext/ifconfig.py index bad5953d3..1768acf18 100644 --- a/sphinx/ext/ifconfig.py +++ b/sphinx/ext/ifconfig.py @@ -23,6 +23,7 @@ from docutils import nodes import sphinx from sphinx.util.docutils import SphinxDirective +from sphinx.util.nodes import nested_parse_with_titles if False: # For type annotation @@ -48,8 +49,7 @@ class IfConfig(SphinxDirective): node.document = self.state.document self.set_source_info(node) node['expr'] = self.arguments[0] - self.state.nested_parse(self.content, self.content_offset, - node, match_titles=True) + nested_parse_with_titles(self.state, self.content, node) return [node] diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 00a41afe8..0fea99fb8 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -100,7 +100,7 @@ class GoogleDocstring: """ - _name_rgx = re.compile(r"^\s*(:(?P<role>\w+):`(?P<name>[a-zA-Z0-9_.-]+)`|" + _name_rgx = re.compile(r"^\s*((?::(?P<role>\S+):)?`(?P<name>[a-zA-Z0-9_.-]+)`|" r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X) def __init__(self, docstring, config=None, app=None, what='', name='', @@ -700,9 +700,9 @@ class GoogleDocstring: fields = self._consume_fields(parse_type=False, prefer_type=True) lines = [] # type: List[str] for _name, _type, _desc in fields: - m = self._name_rgx.match(_type).groupdict() - if m['role']: - _type = m['name'] + m = self._name_rgx.match(_type) + if m and m.group('name'): + _type = m.group('name') _type = ' ' + _type if _type else '' _desc = self._strip_empty(_desc) _descs = ' ' + '\n '.join(_desc) if any(_desc) else '' diff --git a/sphinx/templates/quickstart/Makefile.new_t b/sphinx/templates/quickstart/Makefile.new_t index 16a9d482f..7532398b2 100644 --- a/sphinx/templates/quickstart/Makefile.new_t +++ b/sphinx/templates/quickstart/Makefile.new_t @@ -1,11 +1,14 @@ # Minimal makefile for Sphinx documentation # -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SOURCEDIR = {{ rsrcdir }} -BUILDDIR = {{ rbuilddir }} +# You can set these variables from the command line. For example: +# SPHINXOPTS='-E -W -n' make html +# will run the html builder in a clean environment (-E), treating warnings +# as errors (-W), in nitpicky mode (-n). +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR ?= {{ rsrcdir }} +BUILDDIR ?= {{ rbuilddir }} # Put it first so that "make" without argument is like "make help". help: @@ -17,3 +20,4 @@ help: # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 4c5826411..bdc270655 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -75,6 +75,16 @@ var Search = { } }, + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, + dataType: "script", cache: true, + complete: function(jqxhr, textstatus) { + if (textstatus != "success") { + document.getElementById("searchindexloader").src = url; + } + }}); + }, + setIndex : function(index) { var q; this._index = index; diff --git a/sphinx/themes/bizstyle/static/bizstyle.css_t b/sphinx/themes/bizstyle/static/bizstyle.css_t index 949d86c6a..f2b400688 100644 --- a/sphinx/themes/bizstyle/static/bizstyle.css_t +++ b/sphinx/themes/bizstyle/static/bizstyle.css_t @@ -410,6 +410,20 @@ p.versionchanged span.versionmodified { background-color: #DCE6A0; } +dl.field-list > dt { + color: white; + padding-left: 0.5em; + padding-right: 5px; + background-color: #82A0BE; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + background-color: #f7f7f7; +} + /* -- table styles ---------------------------------------------------------- */ table.docutils { diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index 2fc1b6e72..16849c46c 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -9,7 +9,6 @@ """ import re -from typing import cast from docutils import nodes from docutils.transforms import Transform, Transformer @@ -19,13 +18,12 @@ from docutils.utils import normalize_language_tag from docutils.utils.smartquotes import smartchars from sphinx import addnodes +from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.locale import _, __ from sphinx.util import logging from sphinx.util.docutils import new_document from sphinx.util.i18n import format_date -from sphinx.util.nodes import ( - NodeMatcher, apply_source_workaround, copy_source_info, is_smartquotable -) +from sphinx.util.nodes import NodeMatcher, apply_source_workaround, is_smartquotable if False: # For type annotation @@ -200,39 +198,6 @@ class SortIds(SphinxTransform): node['ids'] = node['ids'][1:] + [node['ids'][0]] -class SmartQuotesSkipper(SphinxTransform): - """Mark specific nodes as not smartquoted.""" - default_priority = 619 - - def apply(self, **kwargs): - # type: (Any) -> None - # citation labels - for node in self.document.traverse(nodes.citation): - label = cast(nodes.label, node[0]) - label['support_smartquotes'] = False - - -class CitationReferences(SphinxTransform): - """ - Replace citation references by pending_xref nodes before the default - docutils transform tries to resolve them. - """ - default_priority = 619 - - def apply(self, **kwargs): - # type: (Any) -> None - for node in self.document.traverse(nodes.citation_reference): - target = node.astext() - ref = addnodes.pending_xref(target, refdomain='std', reftype='citation', - reftarget=target, refwarn=True, - support_smartquotes=False, - ids=node["ids"], - classes=node.get('classes', [])) - ref += nodes.inline(target, '[%s]' % target) - copy_source_info(node, ref) - node.replace_self(ref) - - TRANSLATABLE_NODES = { 'literal-block': nodes.literal_block, 'doctest-block': nodes.doctest_block, @@ -440,12 +405,22 @@ class ManpageLink(SphinxTransform): node.attributes.update(info) +from sphinx.domains.citation import ( # NOQA + CitationDefinitionTransform, CitationReferenceTransform +) + +deprecated_alias('sphinx.transforms', + { + 'CitationReferences': CitationReferenceTransform, + 'SmartQuotesSkipper': CitationDefinitionTransform, + }, + RemovedInSphinx40Warning) + + def setup(app): # type: (Sphinx) -> Dict[str, Any] app.add_transform(ApplySourceWorkaround) app.add_transform(ExtraTranslatableNodes) - app.add_transform(SmartQuotesSkipper) - app.add_transform(CitationReferences) app.add_transform(DefaultSubstitutions) app.add_transform(MoveModuleTargets) app.add_transform(HandleCodeBlocks) diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 4cd646f96..fe98783e7 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -20,6 +20,7 @@ from io import StringIO from os import path from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.testing.path import path as Path if False: # For type annotation @@ -167,15 +168,18 @@ fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() def abspath(pathdir): # type: (str) -> str - pathdir = path.abspath(pathdir) - if isinstance(pathdir, bytes): - try: - pathdir = pathdir.decode(fs_encoding) - except UnicodeDecodeError: - raise UnicodeDecodeError('multibyte filename not supported on ' - 'this filesystem encoding ' - '(%r)' % fs_encoding) - return pathdir + if isinstance(pathdir, Path): + return pathdir.abspath() + else: + pathdir = path.abspath(pathdir) + if isinstance(pathdir, bytes): + try: + pathdir = pathdir.decode(fs_encoding) + except UnicodeDecodeError: + raise UnicodeDecodeError('multibyte filename not supported on ' + 'this filesystem encoding ' + '(%r)' % fs_encoding) + return pathdir def getcwd(): diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index afab35950..b78dd1fcf 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -67,6 +67,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): self.param_separator = '' self.optional_param_level = 0 self._table_row_index = 0 + self._fieldlist_row_index = 0 self.required_params_left = 0 def visit_start_of_file(self, node): diff --git a/tests/roots/test-ext-autosummary-imported_members/autosummary_dummy_package/__init__.py b/tests/roots/test-ext-autosummary-imported_members/autosummary_dummy_package/__init__.py new file mode 100644 index 000000000..0a7d9f382 --- /dev/null +++ b/tests/roots/test-ext-autosummary-imported_members/autosummary_dummy_package/__init__.py @@ -0,0 +1 @@ +from .autosummary_dummy_module import Bar, foo diff --git a/tests/roots/test-ext-autosummary-imported_members/autosummary_dummy_package/autosummary_dummy_module.py b/tests/roots/test-ext-autosummary-imported_members/autosummary_dummy_package/autosummary_dummy_module.py new file mode 100644 index 000000000..9c93f064e --- /dev/null +++ b/tests/roots/test-ext-autosummary-imported_members/autosummary_dummy_package/autosummary_dummy_module.py @@ -0,0 +1,8 @@ +class Bar: + """Bar class""" + pass + + +def foo(): + """Foo function""" + pass diff --git a/tests/roots/test-ext-autosummary-imported_members/conf.py b/tests/roots/test-ext-autosummary-imported_members/conf.py new file mode 100644 index 000000000..4cfff02dc --- /dev/null +++ b/tests/roots/test-ext-autosummary-imported_members/conf.py @@ -0,0 +1,7 @@ +import os +import sys +sys.path.insert(0, os.path.abspath('.')) + +extensions = ['sphinx.ext.autosummary'] +autosummary_generate = True +autosummary_imported_members = True diff --git a/tests/roots/test-ext-autosummary-imported_members/index.rst b/tests/roots/test-ext-autosummary-imported_members/index.rst new file mode 100644 index 000000000..608ca2954 --- /dev/null +++ b/tests/roots/test-ext-autosummary-imported_members/index.rst @@ -0,0 +1,7 @@ +test-ext-autosummary-mock_imports +================================= + +.. autosummary:: + :toctree: generated + + autosummary_dummy_package diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 46019b4a9..d6470dc7c 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -755,6 +755,20 @@ def test_attributes(): check('member', 'int *[[attr]] *i', {1: 'i__iPP', 2: '1i'}) +def test_xref_parsing(): + def check(target): + class Config: + cpp_id_attributes = ["id_attr"] + cpp_paren_attributes = ["paren_attr"] + parser = DefinitionParser(target, None, Config()) + ast, isShorthand = parser.parse_xref_object() + parser.assert_end() + check('f') + check('f()') + check('void f()') + check('T f()') + + # def test_print(): # # used for getting all the ids out for checking # for a in ids: diff --git a/tests/test_domain_std.py b/tests/test_domain_std.py index 15daeeea6..dd77c11dd 100644 --- a/tests/test_domain_std.py +++ b/tests/test_domain_std.py @@ -11,8 +11,12 @@ from unittest import mock from docutils import nodes +from docutils.nodes import definition, definition_list, definition_list_item, term +from sphinx.addnodes import glossary, index from sphinx.domains.std import StandardDomain +from sphinx.testing import restructuredtext +from sphinx.testing.util import assert_node def test_process_doc_handle_figure_caption(): @@ -80,3 +84,158 @@ def test_get_full_qualified_name(): kwargs = {'std:program': 'ls'} node = nodes.reference(reftype='option', reftarget='-l', **kwargs) assert domain.get_full_qualified_name(node) == 'ls.-l' + + +def test_glossary(app): + text = (".. glossary::\n" + "\n" + " term1\n" + " term2\n" + " description\n" + "\n" + " term3 : classifier\n" + " description\n" + " description\n" + "\n" + " term4 : class1 : class2\n" + " description\n") + + # doctree + doctree = restructuredtext.parse(app, text) + assert_node(doctree, ( + [glossary, definition_list, ([definition_list_item, ([term, ("term1", + index)], + [term, ("term2", + index)], + definition)], + [definition_list_item, ([term, ("term3", + index)], + definition)], + [definition_list_item, ([term, ("term4", + index)], + definition)])], + )) + assert_node(doctree[0][0][0][0][1], + entries=[("single", "term1", "term-term1", "main", None)]) + assert_node(doctree[0][0][0][1][1], + entries=[("single", "term2", "term-term2", "main", None)]) + assert_node(doctree[0][0][0][2], + [definition, nodes.paragraph, "description"]) + assert_node(doctree[0][0][1][0][1], + entries=[("single", "term3", "term-term3", "main", "classifier")]) + assert_node(doctree[0][0][1][1], + [definition, nodes.paragraph, ("description\n" + "description")]) + assert_node(doctree[0][0][2][0][1], + entries=[("single", "term4", "term-term4", "main", "class1")]) + assert_node(doctree[0][0][2][1], + [nodes.definition, nodes.paragraph, "description"]) + + # index + objects = list(app.env.get_domain("std").get_objects()) + assert ("term1", "term1", "term", "index", "term-term1", -1) in objects + assert ("term2", "term2", "term", "index", "term-term2", -1) in objects + assert ("term3", "term3", "term", "index", "term-term3", -1) in objects + assert ("term4", "term4", "term", "index", "term-term4", -1) in objects + + +def test_glossary_warning(app, status, warning): + # empty line between terms + text = (".. glossary::\n" + "\n" + " term1\n" + "\n" + " term2\n") + restructuredtext.parse(app, text, "case1") + assert ("case1.rst:4: WARNING: glossary terms must not be separated by empty lines" + in warning.getvalue()) + + # glossary starts with indented item + text = (".. glossary::\n" + "\n" + " description\n" + " term\n") + restructuredtext.parse(app, text, "case2") + assert ("case2.rst:3: WARNING: glossary term must be preceded by empty line" + in warning.getvalue()) + + # empty line between terms + text = (".. glossary::\n" + "\n" + " term1\n" + " description\n" + " term2\n") + restructuredtext.parse(app, text, "case3") + assert ("case3.rst:4: WARNING: glossary term must be preceded by empty line" + in warning.getvalue()) + + +def test_glossary_comment(app): + text = (".. glossary::\n" + "\n" + " term1\n" + " description\n" + " .. term2\n" + " description\n" + " description\n") + doctree = restructuredtext.parse(app, text) + assert_node(doctree, ( + [glossary, definition_list, definition_list_item, ([term, ("term1", + index)], + definition)], + )) + assert_node(doctree[0][0][0][1], + [nodes.definition, nodes.paragraph, "description"]) + + +def test_glossary_comment2(app): + text = (".. glossary::\n" + "\n" + " term1\n" + " description\n" + "\n" + " .. term2\n" + " term3\n" + " description\n" + " description\n") + doctree = restructuredtext.parse(app, text) + assert_node(doctree, ( + [glossary, definition_list, ([definition_list_item, ([term, ("term1", + index)], + definition)], + [definition_list_item, ([term, ("term3", + index)], + definition)])], + )) + assert_node(doctree[0][0][0][1], + [nodes.definition, nodes.paragraph, "description"]) + assert_node(doctree[0][0][1][1], + [nodes.definition, nodes.paragraph, ("description\n" + "description")]) + + +def test_glossary_sorted(app): + text = (".. glossary::\n" + " :sorted:\n" + "\n" + " term3\n" + " description\n" + "\n" + " term2\n" + " term1\n" + " description\n") + doctree = restructuredtext.parse(app, text) + assert_node(doctree, ( + [glossary, definition_list, ([definition_list_item, ([term, ("term2", + index)], + [term, ("term1", + index)], + definition)], + [definition_list_item, ([term, ("term3", + index)], + definition)])], + )) + assert_node(doctree[0][0][0][2], + [nodes.definition, nodes.paragraph, "description"]) + assert_node(doctree[0][0][1][1], + [nodes.definition, nodes.paragraph, "description"]) diff --git a/tests/test_environment_indexentries.py b/tests/test_environment_indexentries.py index 62e4ffb79..ec76acdc0 100644 --- a/tests/test_environment_indexentries.py +++ b/tests/test_environment_indexentries.py @@ -8,135 +8,116 @@ :license: BSD, see LICENSE for details. """ -from collections import namedtuple -from unittest import mock +import pytest -from sphinx import locale from sphinx.environment.adapters.indexentries import IndexEntries - -Environment = namedtuple('Environment', 'indexentries') - -dummy_builder = mock.Mock() -dummy_builder.get_relative_uri.return_value = '' - - -def test_create_single_index(): - # type, value, tid, main, index_key - env = Environment({ - 'index': [ - ('single', 'docutils', 'id1', '', None), - ('single', 'Python', 'id2', '', None), - ('single', 'pip; install', 'id3', '', None), - ('single', 'pip; upgrade', 'id4', '', None), - ('single', 'Sphinx', 'id5', '', None), - ('single', 'Ель', 'id6', '', None), - ('single', 'ёлка', 'id7', '', None), - ('single', 'תירבע', 'id8', '', None), - ('single', '9-symbol', 'id9', '', None), - ('single', '&-symbol', 'id10', '', None), - ], - }) - index = IndexEntries(env).create_index(dummy_builder) +from sphinx.testing import restructuredtext + + +@pytest.mark.sphinx('dummy') +def test_create_single_index(app): + app.env.indexentries.clear() + text = (".. index:: docutils\n" + ".. index:: Python\n" + ".. index:: pip; install\n" + ".. index:: pip; upgrade\n" + ".. index:: Sphinx\n" + ".. index:: Ель\n" + ".. index:: ёлка\n" + ".. index:: תירבע\n" + ".. index:: 9-symbol\n" + ".. index:: &-symbol\n") + restructuredtext.parse(app, text) + index = IndexEntries(app.env).create_index(app.builder) assert len(index) == 6 - assert index[0] == ('Symbols', [('&-symbol', [[('', '#id10')], [], None]), - ('9-symbol', [[('', '#id9')], [], None])]) - assert index[1] == ('D', [('docutils', [[('', '#id1')], [], None])]) - assert index[2] == ('P', [('pip', [[], [('install', [('', '#id3')]), - ('upgrade', [('', '#id4')])], None]), - ('Python', [[('', '#id2')], [], None])]) - assert index[3] == ('S', [('Sphinx', [[('', '#id5')], [], None])]) - assert index[4] == ('Е', [('ёлка', [[('', '#id7')], [], None]), - ('Ель', [[('', '#id6')], [], None])]) - assert index[5] == ('ת', [('תירבע', [[('', '#id8')], [], None])]) - - -def test_create_pair_index(): - # type, value, tid, main, index_key - env = Environment({ - 'index': [ - ('pair', 'docutils; reStructuredText', 'id1', '', None), - ('pair', 'Python; interpreter', 'id2', '', None), - ('pair', 'Sphinx; documentation tool', 'id3', '', None), - ], - }) - index = IndexEntries(env).create_index(dummy_builder) + assert index[0] == ('Symbols', [('&-symbol', [[('', '#index-9')], [], None]), + ('9-symbol', [[('', '#index-8')], [], None])]) + assert index[1] == ('D', [('docutils', [[('', '#index-0')], [], None])]) + assert index[2] == ('P', [('pip', [[], [('install', [('', '#index-2')]), + ('upgrade', [('', '#index-3')])], None]), + ('Python', [[('', '#index-1')], [], None])]) + assert index[3] == ('S', [('Sphinx', [[('', '#index-4')], [], None])]) + assert index[4] == ('Е', [('ёлка', [[('', '#index-6')], [], None]), + ('Ель', [[('', '#index-5')], [], None])]) + assert index[5] == ('ת', [('תירבע', [[('', '#index-7')], [], None])]) + + +@pytest.mark.sphinx('dummy') +def test_create_pair_index(app): + app.env.indexentries.clear() + text = (".. index:: pair: docutils; reStructuredText\n" + ".. index:: pair: Python; interpreter\n" + ".. index:: pair: Sphinx; documentation tool\n") + restructuredtext.parse(app, text) + index = IndexEntries(app.env).create_index(app.builder) assert len(index) == 5 assert index[0] == ('D', - [('documentation tool', [[], [('Sphinx', [('', '#id3')])], None]), - ('docutils', [[], [('reStructuredText', [('', '#id1')])], None])]) - assert index[1] == ('I', [('interpreter', [[], [('Python', [('', '#id2')])], None])]) - assert index[2] == ('P', [('Python', [[], [('interpreter', [('', '#id2')])], None])]) + [('documentation tool', [[], [('Sphinx', [('', '#index-2')])], None]), + ('docutils', [[], [('reStructuredText', [('', '#index-0')])], None])]) + assert index[1] == ('I', [('interpreter', [[], [('Python', [('', '#index-1')])], None])]) + assert index[2] == ('P', [('Python', [[], [('interpreter', [('', '#index-1')])], None])]) assert index[3] == ('R', - [('reStructuredText', [[], [('docutils', [('', '#id1')])], None])]) + [('reStructuredText', [[], [('docutils', [('', '#index-0')])], None])]) assert index[4] == ('S', - [('Sphinx', [[], [('documentation tool', [('', '#id3')])], None])]) - - -def test_create_triple_index(): - # type, value, tid, main, index_key - env = Environment({ - 'index': [ - ('triple', 'foo; bar; baz', 'id1', '', None), - ('triple', 'Python; Sphinx; reST', 'id2', '', None), - ], - }) - index = IndexEntries(env).create_index(dummy_builder) + [('Sphinx', [[], [('documentation tool', [('', '#index-2')])], None])]) + + +@pytest.mark.sphinx('dummy') +def test_create_triple_index(app): + app.env.indexentries.clear() + text = (".. index:: triple: foo; bar; baz\n" + ".. index:: triple: Python; Sphinx; reST\n") + restructuredtext.parse(app, text) + index = IndexEntries(app.env).create_index(app.builder) assert len(index) == 5 - assert index[0] == ('B', [('bar', [[], [('baz, foo', [('', '#id1')])], None]), - ('baz', [[], [('foo bar', [('', '#id1')])], None])]) - assert index[1] == ('F', [('foo', [[], [('bar baz', [('', '#id1')])], None])]) - assert index[2] == ('P', [('Python', [[], [('Sphinx reST', [('', '#id2')])], None])]) - assert index[3] == ('R', [('reST', [[], [('Python Sphinx', [('', '#id2')])], None])]) - assert index[4] == ('S', [('Sphinx', [[], [('reST, Python', [('', '#id2')])], None])]) - - -def test_create_see_index(): - locale.init([], None) - - # type, value, tid, main, index_key - env = Environment({ - 'index': [ - ('see', 'docutils; reStructuredText', 'id1', '', None), - ('see', 'Python; interpreter', 'id2', '', None), - ('see', 'Sphinx; documentation tool', 'id3', '', None), - ], - }) - index = IndexEntries(env).create_index(dummy_builder) + assert index[0] == ('B', [('bar', [[], [('baz, foo', [('', '#index-0')])], None]), + ('baz', [[], [('foo bar', [('', '#index-0')])], None])]) + assert index[1] == ('F', [('foo', [[], [('bar baz', [('', '#index-0')])], None])]) + assert index[2] == ('P', [('Python', [[], [('Sphinx reST', [('', '#index-1')])], None])]) + assert index[3] == ('R', [('reST', [[], [('Python Sphinx', [('', '#index-1')])], None])]) + assert index[4] == ('S', [('Sphinx', [[], [('reST, Python', [('', '#index-1')])], None])]) + + +@pytest.mark.sphinx('dummy') +def test_create_see_index(app): + app.env.indexentries.clear() + text = (".. index:: see: docutils; reStructuredText\n" + ".. index:: see: Python; interpreter\n" + ".. index:: see: Sphinx; documentation tool\n") + restructuredtext.parse(app, text) + index = IndexEntries(app.env).create_index(app.builder) assert len(index) == 3 assert index[0] == ('D', [('docutils', [[], [('see reStructuredText', [])], None])]) assert index[1] == ('P', [('Python', [[], [('see interpreter', [])], None])]) assert index[2] == ('S', [('Sphinx', [[], [('see documentation tool', [])], None])]) -def test_create_seealso_index(): - locale.init([], None) - - # type, value, tid, main, index_key - env = Environment({ - 'index': [ - ('seealso', 'docutils; reStructuredText', 'id1', '', None), - ('seealso', 'Python; interpreter', 'id2', '', None), - ('seealso', 'Sphinx; documentation tool', 'id3', '', None), - ], - }) - index = IndexEntries(env).create_index(dummy_builder) +@pytest.mark.sphinx('dummy') +def test_create_seealso_index(app): + app.env.indexentries.clear() + text = (".. index:: seealso: docutils; reStructuredText\n" + ".. index:: seealso: Python; interpreter\n" + ".. index:: seealso: Sphinx; documentation tool\n") + restructuredtext.parse(app, text) + index = IndexEntries(app.env).create_index(app.builder) assert len(index) == 3 assert index[0] == ('D', [('docutils', [[], [('see also reStructuredText', [])], None])]) assert index[1] == ('P', [('Python', [[], [('see also interpreter', [])], None])]) assert index[2] == ('S', [('Sphinx', [[], [('see also documentation tool', [])], None])]) -def test_create_index_by_key(): - # type, value, tid, main, index_key - env = Environment({ - 'index': [ - ('single', 'docutils', 'id1', '', None), - ('single', 'Python', 'id2', '', None), - ('single', 'スフィンクス', 'id3', '', 'ス'), - ], - }) - index = IndexEntries(env).create_index(dummy_builder) +@pytest.mark.sphinx('dummy') +def test_create_index_by_key(app): + app.env.indexentries.clear() + # At present, only glossary directive is able to create index key + text = (".. glossary::\n" + "\n" + " docutils\n" + " Python\n" + " スフィンクス : ス\n") + restructuredtext.parse(app, text) + index = IndexEntries(app.env).create_index(app.builder) assert len(index) == 3 - assert index[0] == ('D', [('docutils', [[('', '#id1')], [], None])]) - assert index[1] == ('P', [('Python', [[('', '#id2')], [], None])]) - assert index[2] == ('ス', [('スフィンクス', [[('', '#id3')], [], 'ス'])]) + assert index[0] == ('D', [('docutils', [[('main', '#term-docutils')], [], None])]) + assert index[1] == ('P', [('Python', [[('main', '#term-python')], [], None])]) + assert index[2] == ('ス', [('スフィンクス', [[('main', '#term-2')], [], 'ス'])]) diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index 3cc9710d8..2ccfd9342 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -242,3 +242,23 @@ def test_autosummary_mock_imports(app, status, warning): assert app.env.get_doctree('generated/foo') finally: sys.modules.pop('foo', None) # unload foo module + + +@pytest.mark.sphinx('dummy', testroot='ext-autosummary-imported_members') +def test_autosummary_imported_members(app, status, warning): + try: + app.build() + # generated/foo is generated successfully + assert app.env.get_doctree('generated/autosummary_dummy_package') + + module = (app.srcdir / 'generated' / 'autosummary_dummy_package.rst').text() + assert (' .. autosummary::\n' + ' \n' + ' Bar\n' + ' \n' in module) + assert (' .. autosummary::\n' + ' \n' + ' foo\n' + ' \n' in module) + finally: + sys.modules.pop('autosummary_dummy_package', None) diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py index 03b5bb689..30ad625aa 100644 --- a/tests/test_ext_inheritance_diagram.py +++ b/tests/test_ext_inheritance_diagram.py @@ -90,7 +90,7 @@ def test_inheritance_diagram_latex_alias(app, status, warning): def test_import_classes(rootdir): - from sphinx.application import Sphinx, TemplateBridge + from sphinx.parsers import Parser, RSTParser from sphinx.util.i18n import CatalogInfo try: @@ -120,16 +120,16 @@ def test_import_classes(rootdir): assert classes == [] # all of classes in the module - classes = import_classes('sphinx.application', None) - assert set(classes) == {Sphinx, TemplateBridge} + classes = import_classes('sphinx.parsers', None) + assert set(classes) == {Parser, RSTParser} # specified class in the module - classes = import_classes('sphinx.application.Sphinx', None) - assert classes == [Sphinx] + classes = import_classes('sphinx.parsers.Parser', None) + assert classes == [Parser] # specified class in current module - classes = import_classes('Sphinx', 'sphinx.application') - assert classes == [Sphinx] + classes = import_classes('Parser', 'sphinx.parsers') + assert classes == [Parser] # relative module name to current module classes = import_classes('i18n.CatalogInfo', 'sphinx.util') diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 86ded7d89..a333dc47b 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -473,12 +473,21 @@ Raises: A setting wasn't specified, or was invalid. ValueError: Something something value error. + :py:class:`AttributeError` + errors for missing attributes. + ~InvalidDimensionsError + If the dimensions couldn't be parsed. + `InvalidArgumentsError` + If the arguments are invalid. """, """ Example Function :raises RuntimeError: A setting wasn't specified, or was invalid. :raises ValueError: Something something value error. +:raises AttributeError: errors for missing attributes. +:raises ~InvalidDimensionsError: If the dimensions couldn't be parsed. +:raises InvalidArgumentsError: If the arguments are invalid. """), ################################ (""" |