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