diff --git a/CHANGES b/CHANGES index ea076e974..2568168f2 100644 --- a/CHANGES +++ b/CHANGES @@ -84,9 +84,11 @@ Deprecated * ``env._read_parallel()`` is deprecated * ``env.write_doctree()`` is deprecated * ``env._nitpick_ignore`` is deprecated +* ``env.versionchanges`` is deprecated * ``env.dump()`` is deprecated * ``env.dumps()`` is deprecated * ``env.topickle()`` is deprecated +* ``env.note_versionchange()`` is deprecated * ``sphinx.writers.latex.Table.caption_footnotetexts`` is deprecated * ``sphinx.writers.latex.Table.header_footnotetexts`` is deprecated * ``sphinx.writers.latex.LaTeXTranslator.footnotestack`` is deprecated @@ -116,6 +118,7 @@ Deprecated * ``sphinx.ext.mathbase.is_in_section_title()`` is deprecated * ``sphinx.ext.mathbase.MathDomain`` is deprecated * ``sphinx.ext.mathbase.setup_math()`` is deprecated +* ``sphinx.directives.other.VersionChanges`` is deprecated * ``sphinx.highlighting.PygmentsBridge.unhighlight()`` is deprecated * ``sphinx.ext.mathbase.get_node_equation_number()`` is deprecated * ``sphinx.ext.mathbase.wrap_displaymath()`` is deprecated diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 55f7fb827..0fa4db260 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -131,6 +131,11 @@ The following is a list of deprecated interface. - 4.0 - :meth:`~sphinx.application.Sphinx.add_js_file()` + * - ``sphinx.directives.other.VersionChanges`` + - 1.8 + - 3.0 + - ``sphinx.domains.changeset.VersionChanges`` + * - ``sphinx.highlighting.PygmentsBridge.unhighlight()`` - 1.8 - 3.0 @@ -385,6 +390,11 @@ The following is a list of deprecated interface. - 3.0 - :confval:`nitpick_ignore` + * - ``BuildEnvironment.versionchanges`` + - 1.8 + - 3.0 + - N/A + * - ``BuildEnvironment.update()`` - 1.8 - 3.0 @@ -410,6 +420,11 @@ The following is a list of deprecated interface. - 3.0 - ``Builder.write_doctree()`` + * - ``BuildEnvironment.note_versionchange()`` + - 1.8 + - 3.0 + - ``ChangesDomain.note_changeset()`` + * - ``warn()`` (template helper function) - 1.8 - 3.0 diff --git a/sphinx/application.py b/sphinx/application.py index 5c6bfc52e..adca8acb4 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -80,6 +80,7 @@ builtin_extensions = ( 'sphinx.builders.xml', 'sphinx.config', 'sphinx.domains.c', + 'sphinx.domains.changeset', 'sphinx.domains.cpp', 'sphinx.domains.javascript', 'sphinx.domains.math', diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index 011aa6ebe..3f9bffa0d 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -11,11 +11,13 @@ import codecs from os import path +from typing import cast from six import iteritems from sphinx import package_dir from sphinx.builders import Builder +from sphinx.domains.changeset import ChangeSetDomain from sphinx.locale import _, __ from sphinx.theming import HTMLThemeFactory from sphinx.util import logging @@ -60,6 +62,7 @@ class ChangesBuilder(Builder): def write(self, *ignored): # type: (Any) -> None version = self.config.version + domain = cast(ChangeSetDomain, self.env.get_domain('changeset')) libchanges = {} # type: Dict[unicode, List[Tuple[unicode, unicode, int]]] apichanges = [] # type: List[Tuple[unicode, unicode, int]] otherchanges = {} # type: Dict[Tuple[unicode, unicode], List[Tuple[unicode, unicode, int]]] # NOQA @@ -67,21 +70,22 @@ class ChangesBuilder(Builder): logger.info(bold(__('no changes in version %s.') % version)) return logger.info(bold('writing summary file...')) - for type, docname, lineno, module, descname, content in \ - self.env.versionchanges[version]: - if isinstance(descname, tuple): - descname = descname[0] - ttext = self.typemap[type] - context = content.replace('\n', ' ') - if descname and docname.startswith('c-api'): + for changeset in domain.get_changesets_for(version): + if isinstance(changeset.descname, tuple): + descname = changeset.descname[0] + else: + descname = changeset.descname + ttext = self.typemap[changeset.type] + context = changeset.content.replace('\n', ' ') + if descname and changeset.docname.startswith('c-api'): if context: entry = '%s: %s: %s' % (descname, ttext, context) else: entry = '%s: %s.' % (descname, ttext) - apichanges.append((entry, docname, lineno)) - elif descname or module: - if not module: + apichanges.append((entry, changeset.docname, changeset.lineno)) + elif descname or changeset.module: + if not changeset.module: module = _('Builtins') if not descname: descname = _('Module level') @@ -90,15 +94,15 @@ class ChangesBuilder(Builder): context) else: entry = '%s: %s.' % (descname, ttext) - libchanges.setdefault(module, []).append((entry, docname, - lineno)) + libchanges.setdefault(module, []).append((entry, changeset.docname, + changeset.lineno)) else: if not context: continue entry = '%s: %s' % (ttext.capitalize(), context) - title = self.env.titles[docname].astext() - otherchanges.setdefault((docname, title), []).append( - (entry, docname, lineno)) + title = self.env.titles[changeset.docname].astext() + otherchanges.setdefault((changeset.docname, title), []).append( + (entry, changeset.docname, changeset.lineno)) ctx = { 'project': self.config.project, diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index c6c82ff43..2e44feb95 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -17,8 +17,8 @@ from docutils.parsers.rst.directives.misc import Class from docutils.parsers.rst.directives.misc import Include as BaseInclude from six.moves import range -from sphinx import addnodes, locale -from sphinx.deprecation import DeprecatedDict, RemovedInSphinx30Warning +from sphinx import addnodes +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 @@ -32,19 +32,6 @@ if False: from sphinx.application import Sphinx # NOQA -versionlabels = { - 'versionadded': _('New in version %s'), - 'versionchanged': _('Changed in version %s'), - 'deprecated': _('Deprecated since version %s'), -} # type: Dict[unicode, unicode] - -locale.versionlabels = DeprecatedDict( - versionlabels, - 'sphinx.locale.versionlabels is deprecated. ' - 'Please use sphinx.directives.other.versionlabels instead.', - RemovedInSphinx30Warning -) - glob_re = re.compile(r'.*[*?\[].*') @@ -218,54 +205,6 @@ class Index(SphinxDirective): return [indexnode, targetnode] -class VersionChange(SphinxDirective): - """ - Directive to describe a change/addition/deprecation in a specific version. - """ - has_content = True - required_arguments = 1 - optional_arguments = 1 - final_argument_whitespace = True - option_spec = {} # type: Dict - - def run(self): - # type: () -> List[nodes.Node] - node = addnodes.versionmodified() - node.document = self.state.document - set_source_info(self, node) - node['type'] = self.name - node['version'] = self.arguments[0] - text = versionlabels[self.name] % self.arguments[0] - if len(self.arguments) == 2: - inodes, messages = self.state.inline_text(self.arguments[1], - self.lineno + 1) - para = nodes.paragraph(self.arguments[1], '', *inodes, translatable=False) - set_source_info(self, para) - node.append(para) - else: - messages = [] - if self.content: - self.state.nested_parse(self.content, self.content_offset, node) - if len(node): - if isinstance(node[0], nodes.paragraph) and node[0].rawsource: - content = nodes.inline(node[0].rawsource, translatable=True) - content.source = node[0].source - content.line = node[0].line - content += node[0].children - node[0].replace_self(nodes.paragraph('', '', content, translatable=False)) - node[0].insert(0, nodes.inline('', '%s: ' % text, - classes=['versionmodified'])) - else: - para = nodes.paragraph('', '', - nodes.inline('', '%s.' % text, - classes=['versionmodified']), - translatable=False) - node.append(para) - # XXX should record node.source as well - self.env.note_versionchange(node['type'], node['version'], node, node.line) - return [node] + messages - - class SeeAlso(BaseAdmonition): """ An admonition mentioning things to look at as reference. @@ -475,9 +414,6 @@ def setup(app): directives.register_directive('moduleauthor', Author) directives.register_directive('codeauthor', Author) directives.register_directive('index', Index) - directives.register_directive('deprecated', VersionChange) - directives.register_directive('versionadded', VersionChange) - directives.register_directive('versionchanged', VersionChange) directives.register_directive('seealso', SeeAlso) directives.register_directive('tabularcolumns', TabularColumns) directives.register_directive('centered', Centered) diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py new file mode 100644 index 000000000..ed878b1d3 --- /dev/null +++ b/sphinx/domains/changeset.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains.changeset + ~~~~~~~~~~~~~~~~~~~~~~~~ + + The changeset domain. + + :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from typing import NamedTuple + +from docutils import nodes +from six import iteritems + +from sphinx import addnodes +from sphinx import locale +from sphinx.deprecation import DeprecatedDict, RemovedInSphinx30Warning +from sphinx.domains import Domain +from sphinx.locale import _ +from sphinx.util.docutils import SphinxDirective +from sphinx.util.nodes import set_source_info + + +if False: + # For type annotation + from typing import Any, Dict, List # NOQA + from sphinx.application import Sphinx # NOQA + from sphinx.environment import BuildEnvironment # NOQA + + +versionlabels = { + 'versionadded': _('New in version %s'), + 'versionchanged': _('Changed in version %s'), + 'deprecated': _('Deprecated since version %s'), +} # type: Dict[unicode, unicode] + +locale.versionlabels = DeprecatedDict( + versionlabels, + 'sphinx.locale.versionlabels is deprecated. ' + 'Please use sphinx.domains.changeset.versionlabels instead.', + RemovedInSphinx30Warning +) + + +ChangeSet = NamedTuple('ChangeSet', [('type', str), + ('docname', str), + ('lineno', int), + ('module', str), + ('descname', str), + ('content', str)]) + + +class VersionChange(SphinxDirective): + """ + Directive to describe a change/addition/deprecation in a specific version. + """ + has_content = True + required_arguments = 1 + optional_arguments = 1 + final_argument_whitespace = True + option_spec = {} # type: Dict + + def run(self): + # type: () -> List[nodes.Node] + node = addnodes.versionmodified() + node.document = self.state.document + set_source_info(self, node) + node['type'] = self.name + node['version'] = self.arguments[0] + text = versionlabels[self.name] % self.arguments[0] + if len(self.arguments) == 2: + inodes, messages = self.state.inline_text(self.arguments[1], + self.lineno + 1) + para = nodes.paragraph(self.arguments[1], '', *inodes, translatable=False) + set_source_info(self, para) + node.append(para) + else: + messages = [] + if self.content: + self.state.nested_parse(self.content, self.content_offset, node) + if len(node): + if isinstance(node[0], nodes.paragraph) and node[0].rawsource: + content = nodes.inline(node[0].rawsource, translatable=True) + content.source = node[0].source + content.line = node[0].line + content += node[0].children + node[0].replace_self(nodes.paragraph('', '', content, translatable=False)) + node[0].insert(0, nodes.inline('', '%s: ' % text, + classes=['versionmodified'])) + else: + para = nodes.paragraph('', '', + nodes.inline('', '%s.' % text, + classes=['versionmodified']), + translatable=False) + node.append(para) + + self.env.get_domain('changeset').note_changeset(node) # type: ignore + return [node] + messages + + +class ChangeSetDomain(Domain): + """Domain for changesets.""" + + name = 'changeset' + label = 'changeset' + + initial_data = { + 'changes': {}, # version -> list of ChangeSet + } # type: Dict + + def clear_doc(self, docname): + # type: (unicode) -> None + for version, changes in iteritems(self.data['changes']): + for changeset in changes[:]: + if changeset.docname == docname: + changes.remove(changeset) + + def merge_domaindata(self, docnames, otherdata): + # type: (List[unicode], Dict) -> None + # XXX duplicates? + for version, otherchanges in iteritems(otherdata['changes']): + changes = self.data['changes'].setdefault(version, []) + for changeset in otherchanges: + if changeset.docname in docnames: + changes.append(changeset) + + def process_doc(self, env, docname, document): + # type: (BuildEnvironment, unicode, nodes.Node) -> None + pass # nothing to do here. All changesets are registered on calling directive. + + def note_changeset(self, node): + # type: (nodes.Node) -> None + version = node['version'] + module = self.env.ref_context.get('py:module') + objname = self.env.temp_data.get('object') + changeset = ChangeSet(node['type'], self.env.docname, node.line, # type: ignore + module, objname, node.astext()) + self.data['changes'].setdefault(version, []).append(changeset) + + def get_changesets_for(self, version): + # type: (unicode) -> List[ChangeSet] + return self.data['changes'].get(version, []) + + +def setup(app): + # type: (Sphinx) -> Dict[unicode, Any] + app.add_domain(ChangeSetDomain) + app.add_directive('deprecated', VersionChange) + app.add_directive('versionadded', VersionChange) + app.add_directive('versionchanged', VersionChange) + + return { + 'version': 'builtin', + 'env_version': 1, + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 20ff9e336..6a44e16d5 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -186,9 +186,6 @@ class BuildEnvironment(object): self.indexentries = {} # type: Dict[unicode, List[Tuple[unicode, unicode, unicode, unicode, unicode]]] # NOQA # docname -> list of # (type, unicode, target, aliasname) - self.versionchanges = {} # type: Dict[unicode, List[Tuple[unicode, unicode, int, unicode, unicode, unicode]]] # NOQA - # version -> list of (type, docname, - # lineno, module, descname, content) # these map absolute path -> (docnames, unique filename) self.images = FilenameUniqDict() # type: FilenameUniqDict @@ -321,10 +318,6 @@ class BuildEnvironment(object): self.reread_always.discard(docname) self.included.discard(docname) - for version, changes in self.versionchanges.items(): - new = [change for change in changes if change[1] != docname] - changes[:] = new - for domain in self.domains.values(): domain.clear_doc(docname) @@ -343,10 +336,6 @@ class BuildEnvironment(object): if docname in other.included: self.included.add(docname) - for version, changes in other.versionchanges.items(): - self.versionchanges.setdefault(version, []).extend( - change for change in changes if change[1] in docnames) - for domainname, domain in self.domains.items(): domain.merge_domaindata(docnames, other.domaindata[domainname]) app.emit('env-merge-info', self, docnames, other) @@ -571,13 +560,6 @@ class BuildEnvironment(object): """ self.reread_always.add(self.docname) - def note_versionchange(self, type, version, node, lineno): - # type: (unicode, unicode, nodes.Node, int) -> None - self.versionchanges.setdefault(version, []).append( - (type, self.temp_data['docname'], lineno, - self.ref_context.get('py:module'), - self.temp_data.get('object'), node.astext())) - def note_toctree(self, docname, toctreenode): # type: (unicode, addnodes.toctree) -> None """Note a TOC tree directive in a document and gather information about @@ -857,3 +839,21 @@ class BuildEnvironment(object): RemovedInSphinx30Warning) with open(filename, 'wb') as f: self.dump(self, f) + + @property + def versionchanges(self): + # type: () -> Dict[unicode, List[Tuple[unicode, unicode, int, unicode, unicode, unicode]]] # NOQA + warnings.warn('env.versionchanges() is deprecated. ' + 'Please use ChangeSetDomain instead.', + RemovedInSphinx30Warning) + return self.domaindata['changeset']['changes'] + + def note_versionchange(self, type, version, node, lineno): + # type: (unicode, unicode, nodes.Node, int) -> None + warnings.warn('env.note_versionchange() is deprecated. ' + 'Please use ChangeSetDomain.note_changeset() instead.', + RemovedInSphinx30Warning) + node['type'] = type + node['version'] = version + node.line = lineno + self.get_domain('changeset').note_changeset(node) # type: ignore