From e6f090df2bfd776a887dbf6b021094484a2ce3d3 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:23:26 +0000 Subject: [PATCH 1/3] Extract ``changes`` into a new extension --- Doc/conf.py | 1 + Doc/tools/extensions/changes.py | 91 ++++++++++++++++++++++++++++++ Doc/tools/extensions/pyspecific.py | 57 ------------------- 3 files changed, 92 insertions(+), 57 deletions(-) create mode 100644 Doc/tools/extensions/changes.py diff --git a/Doc/conf.py b/Doc/conf.py index b4a924b7bf0857..7b5fbc020c47fd 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -25,6 +25,7 @@ 'audit_events', 'availability', 'c_annotations', + 'changes', 'glossary_search', 'lexers', 'pyspecific', diff --git a/Doc/tools/extensions/changes.py b/Doc/tools/extensions/changes.py new file mode 100644 index 00000000000000..e3713192da4f4f --- /dev/null +++ b/Doc/tools/extensions/changes.py @@ -0,0 +1,91 @@ +"""Support for documenting version of changes, additions, deprecations.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from sphinx.domains.changeset import ( + VersionChange, + versionlabel_classes, + versionlabels, +) +from sphinx.locale import _ as sphinx_gettext + +if TYPE_CHECKING: + from docutils.nodes import Node + + from sphinx.application import Sphinx + from sphinx.util.typing import ExtensionMetadata + + +def expand_version_arg(argument: str, release: str) -> str: + """Expand "next" to the current version""" + if argument == 'next': + return sphinx_gettext('{} (unreleased)').format(release) + return argument + + +class PyVersionChange(VersionChange): + def run(self) -> list[Node]: + # Replace the 'next' special token with the current development version + self.arguments[0] = expand_version_arg( + self.arguments[0], self.config.release + ) + return super().run() + + +class DeprecatedRemoved(VersionChange): + required_arguments = 2 + + _deprecated_label = sphinx_gettext( + 'Deprecated since version %s, will be removed in version %s' + ) + _removed_label = sphinx_gettext( + 'Deprecated since version %s, removed in version %s' + ) + + def run(self) -> list[Node]: + # Replace the first two arguments (deprecated version and removed version) + # with a single tuple of both versions. + version_deprecated = expand_version_arg( + self.arguments[0], self.config.release + ) + version_removed = self.arguments.pop(1) + if version_removed == 'next': + raise ValueError( + 'deprecated-removed:: second argument cannot be `next`' + ) + self.arguments[0] = version_deprecated, version_removed + + # Set the label based on if we have reached the removal version + current_version = tuple(map(int, self.config.version.split('.'))) + removed_version = tuple(map(int, version_removed.split('.'))) + if current_version < removed_version: + versionlabels[self.name] = self._deprecated_label + versionlabel_classes[self.name] = 'deprecated' + else: + versionlabels[self.name] = self._removed_label + versionlabel_classes[self.name] = 'removed' + try: + return super().run() + finally: + # reset versionlabels and versionlabel_classes + versionlabels[self.name] = '' + versionlabel_classes[self.name] = '' + + +def setup(app: Sphinx) -> ExtensionMetadata: + # Override Sphinx's directives with support for 'next' + app.add_directive('versionadded', PyVersionChange, override=True) + app.add_directive('versionchanged', PyVersionChange, override=True) + app.add_directive('versionremoved', PyVersionChange, override=True) + app.add_directive('deprecated', PyVersionChange, override=True) + + # Register the ``.. deprecated-removed::`` directive + app.add_directive('deprecated-removed', DeprecatedRemoved) + + return { + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index bcb8a421e32d09..a6786ff055cecb 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -21,7 +21,6 @@ from docutils.utils import new_document, unescape from sphinx import addnodes from sphinx.builders import Builder -from sphinx.domains.changeset import VersionChange, versionlabels, versionlabel_classes from sphinx.domains.python import PyFunction, PyMethod, PyModule from sphinx.locale import _ as sphinx_gettext from sphinx.util.docutils import SphinxDirective @@ -184,57 +183,6 @@ def run(self): return PyMethod.run(self) -# Support for documenting version of changes, additions, deprecations - -def expand_version_arg(argument, release): - """Expand "next" to the current version""" - if argument == 'next': - return sphinx_gettext('{} (unreleased)').format(release) - return argument - - -class PyVersionChange(VersionChange): - def run(self): - # Replace the 'next' special token with the current development version - self.arguments[0] = expand_version_arg(self.arguments[0], - self.config.release) - return super().run() - - -class DeprecatedRemoved(VersionChange): - required_arguments = 2 - - _deprecated_label = sphinx_gettext('Deprecated since version %s, will be removed in version %s') - _removed_label = sphinx_gettext('Deprecated since version %s, removed in version %s') - - def run(self): - # Replace the first two arguments (deprecated version and removed version) - # with a single tuple of both versions. - version_deprecated = expand_version_arg(self.arguments[0], - self.config.release) - version_removed = self.arguments.pop(1) - if version_removed == 'next': - raise ValueError( - 'deprecated-removed:: second argument cannot be `next`') - self.arguments[0] = version_deprecated, version_removed - - # Set the label based on if we have reached the removal version - current_version = tuple(map(int, self.config.version.split('.'))) - removed_version = tuple(map(int, version_removed.split('.'))) - if current_version < removed_version: - versionlabels[self.name] = self._deprecated_label - versionlabel_classes[self.name] = 'deprecated' - else: - versionlabels[self.name] = self._removed_label - versionlabel_classes[self.name] = 'removed' - try: - return super().run() - finally: - # reset versionlabels and versionlabel_classes - versionlabels[self.name] = '' - versionlabel_classes[self.name] = '' - - # Support for including Misc/NEWS issue_re = re.compile('(?:[Ii]ssue #|bpo-)([0-9]+)', re.I) @@ -417,11 +365,6 @@ def setup(app): app.add_role('issue', issue_role) app.add_role('gh', gh_issue_role) app.add_directive('impl-detail', ImplementationDetail) - app.add_directive('versionadded', PyVersionChange, override=True) - app.add_directive('versionchanged', PyVersionChange, override=True) - app.add_directive('versionremoved', PyVersionChange, override=True) - app.add_directive('deprecated', PyVersionChange, override=True) - app.add_directive('deprecated-removed', DeprecatedRemoved) app.add_builder(PydocTopicsBuilder) app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature) app.add_object_type('pdbcommand', 'pdbcmd', '%s (pdb command)', parse_pdb_command) From 12cced8cd0fd55a03477be98f185f576a9b1822d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:28:04 +0000 Subject: [PATCH 2/3] Lint --- Doc/tools/extensions/changes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/tools/extensions/changes.py b/Doc/tools/extensions/changes.py index e3713192da4f4f..0fbe4fa105a0b1 100644 --- a/Doc/tools/extensions/changes.py +++ b/Doc/tools/extensions/changes.py @@ -13,7 +13,6 @@ if TYPE_CHECKING: from docutils.nodes import Node - from sphinx.application import Sphinx from sphinx.util.typing import ExtensionMetadata From a81d584b0b2e0f2d299a4b885ea83875e956013e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:40:21 +0000 Subject: [PATCH 3/3] Convert to double quotes --- Doc/tools/extensions/changes.py | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Doc/tools/extensions/changes.py b/Doc/tools/extensions/changes.py index 0fbe4fa105a0b1..8de5e7f78c6627 100644 --- a/Doc/tools/extensions/changes.py +++ b/Doc/tools/extensions/changes.py @@ -19,8 +19,8 @@ def expand_version_arg(argument: str, release: str) -> str: """Expand "next" to the current version""" - if argument == 'next': - return sphinx_gettext('{} (unreleased)').format(release) + if argument == "next": + return sphinx_gettext("{} (unreleased)").format(release) return argument @@ -37,10 +37,10 @@ class DeprecatedRemoved(VersionChange): required_arguments = 2 _deprecated_label = sphinx_gettext( - 'Deprecated since version %s, will be removed in version %s' + "Deprecated since version %s, will be removed in version %s" ) _removed_label = sphinx_gettext( - 'Deprecated since version %s, removed in version %s' + "Deprecated since version %s, removed in version %s" ) def run(self) -> list[Node]: @@ -50,38 +50,38 @@ def run(self) -> list[Node]: self.arguments[0], self.config.release ) version_removed = self.arguments.pop(1) - if version_removed == 'next': + if version_removed == "next": raise ValueError( - 'deprecated-removed:: second argument cannot be `next`' + "deprecated-removed:: second argument cannot be `next`" ) self.arguments[0] = version_deprecated, version_removed # Set the label based on if we have reached the removal version - current_version = tuple(map(int, self.config.version.split('.'))) - removed_version = tuple(map(int, version_removed.split('.'))) + current_version = tuple(map(int, self.config.version.split("."))) + removed_version = tuple(map(int, version_removed.split("."))) if current_version < removed_version: versionlabels[self.name] = self._deprecated_label - versionlabel_classes[self.name] = 'deprecated' + versionlabel_classes[self.name] = "deprecated" else: versionlabels[self.name] = self._removed_label - versionlabel_classes[self.name] = 'removed' + versionlabel_classes[self.name] = "removed" try: return super().run() finally: # reset versionlabels and versionlabel_classes - versionlabels[self.name] = '' - versionlabel_classes[self.name] = '' + versionlabels[self.name] = "" + versionlabel_classes[self.name] = "" def setup(app: Sphinx) -> ExtensionMetadata: # Override Sphinx's directives with support for 'next' - app.add_directive('versionadded', PyVersionChange, override=True) - app.add_directive('versionchanged', PyVersionChange, override=True) - app.add_directive('versionremoved', PyVersionChange, override=True) - app.add_directive('deprecated', PyVersionChange, override=True) + app.add_directive("versionadded", PyVersionChange, override=True) + app.add_directive("versionchanged", PyVersionChange, override=True) + app.add_directive("versionremoved", PyVersionChange, override=True) + app.add_directive("deprecated", PyVersionChange, override=True) # Register the ``.. deprecated-removed::`` directive - app.add_directive('deprecated-removed', DeprecatedRemoved) + app.add_directive("deprecated-removed", DeprecatedRemoved) return { "version": "1.0",