162 lines
5.4 KiB
Python
162 lines
5.4 KiB
Python
|
"""The changeset domain."""
|
||
|
|
||
|
from __future__ import annotations
|
||
|
|
||
|
from typing import TYPE_CHECKING, Any, NamedTuple, cast
|
||
|
|
||
|
from docutils import nodes
|
||
|
|
||
|
from sphinx import addnodes
|
||
|
from sphinx.domains import Domain
|
||
|
from sphinx.locale import _
|
||
|
from sphinx.util.docutils import SphinxDirective
|
||
|
|
||
|
if TYPE_CHECKING:
|
||
|
from docutils.nodes import Node
|
||
|
|
||
|
from sphinx.application import Sphinx
|
||
|
from sphinx.environment import BuildEnvironment
|
||
|
from sphinx.util.typing import OptionSpec
|
||
|
|
||
|
|
||
|
versionlabels = {
|
||
|
'versionadded': _('New in version %s'),
|
||
|
'versionchanged': _('Changed in version %s'),
|
||
|
'deprecated': _('Deprecated since version %s'),
|
||
|
}
|
||
|
|
||
|
versionlabel_classes = {
|
||
|
'versionadded': 'added',
|
||
|
'versionchanged': 'changed',
|
||
|
'deprecated': 'deprecated',
|
||
|
}
|
||
|
|
||
|
|
||
|
class ChangeSet(NamedTuple):
|
||
|
type: str
|
||
|
docname: str
|
||
|
lineno: int
|
||
|
module: str | None
|
||
|
descname: str | None
|
||
|
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: OptionSpec = {}
|
||
|
|
||
|
def run(self) -> list[Node]:
|
||
|
node = addnodes.versionmodified()
|
||
|
node.document = self.state.document
|
||
|
self.set_source_info(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)
|
||
|
self.set_source_info(para)
|
||
|
node.append(para)
|
||
|
else:
|
||
|
messages = []
|
||
|
if self.content:
|
||
|
self.state.nested_parse(self.content, self.content_offset, node)
|
||
|
classes = ['versionmodified', versionlabel_classes[self.name]]
|
||
|
if len(node) > 0 and isinstance(node[0], nodes.paragraph):
|
||
|
# the contents start with a paragraph
|
||
|
if node[0].rawsource:
|
||
|
# make the first paragraph translatable
|
||
|
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))
|
||
|
|
||
|
para = node[0]
|
||
|
para.insert(0, nodes.inline('', '%s: ' % text, classes=classes))
|
||
|
elif len(node) > 0:
|
||
|
# the contents do not starts with a paragraph
|
||
|
para = nodes.paragraph('', '',
|
||
|
nodes.inline('', '%s: ' % text, classes=classes),
|
||
|
translatable=False)
|
||
|
node.insert(0, para)
|
||
|
else:
|
||
|
# the contents are empty
|
||
|
para = nodes.paragraph('', '',
|
||
|
nodes.inline('', '%s.' % text, classes=classes),
|
||
|
translatable=False)
|
||
|
node.append(para)
|
||
|
|
||
|
domain = cast(ChangeSetDomain, self.env.get_domain('changeset'))
|
||
|
domain.note_changeset(node)
|
||
|
|
||
|
ret: list[Node] = [node]
|
||
|
ret += messages
|
||
|
return ret
|
||
|
|
||
|
|
||
|
class ChangeSetDomain(Domain):
|
||
|
"""Domain for changesets."""
|
||
|
|
||
|
name = 'changeset'
|
||
|
label = 'changeset'
|
||
|
|
||
|
initial_data: dict[str, Any] = {
|
||
|
'changes': {}, # version -> list of ChangeSet
|
||
|
}
|
||
|
|
||
|
@property
|
||
|
def changesets(self) -> dict[str, list[ChangeSet]]:
|
||
|
return self.data.setdefault('changes', {}) # version -> list of ChangeSet
|
||
|
|
||
|
def note_changeset(self, node: addnodes.versionmodified) -> 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,
|
||
|
module, objname, node.astext())
|
||
|
self.changesets.setdefault(version, []).append(changeset)
|
||
|
|
||
|
def clear_doc(self, docname: str) -> None:
|
||
|
for changes in self.changesets.values():
|
||
|
for changeset in changes[:]:
|
||
|
if changeset.docname == docname:
|
||
|
changes.remove(changeset)
|
||
|
|
||
|
def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> None:
|
||
|
# XXX duplicates?
|
||
|
for version, otherchanges in otherdata['changes'].items():
|
||
|
changes = self.changesets.setdefault(version, [])
|
||
|
for changeset in otherchanges:
|
||
|
if changeset.docname in docnames:
|
||
|
changes.append(changeset)
|
||
|
|
||
|
def process_doc(
|
||
|
self, env: BuildEnvironment, docname: str, document: nodes.document,
|
||
|
) -> None:
|
||
|
pass # nothing to do here. All changesets are registered on calling directive.
|
||
|
|
||
|
def get_changesets_for(self, version: str) -> list[ChangeSet]:
|
||
|
return self.changesets.get(version, [])
|
||
|
|
||
|
|
||
|
def setup(app: Sphinx) -> dict[str, 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,
|
||
|
}
|