Skip to content

Commit df3809c

Browse files
committed
Add soft-deprecated directive
1 parent eb2f634 commit df3809c

File tree

1 file changed

+77
-2
lines changed

1 file changed

+77
-2
lines changed

Doc/tools/extensions/changes.py

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
from __future__ import annotations
44

5-
from typing import TYPE_CHECKING
6-
5+
from docutils import nodes
6+
from sphinx import addnodes
77
from sphinx.domains.changeset import (
88
VersionChange,
99
versionlabel_classes,
1010
versionlabels,
1111
)
1212
from sphinx.locale import _ as sphinx_gettext
1313

14+
TYPE_CHECKING = False
1415
if TYPE_CHECKING:
1516
from docutils.nodes import Node
1617
from sphinx.application import Sphinx
@@ -73,6 +74,77 @@ def run(self) -> list[Node]:
7374
versionlabel_classes[self.name] = ""
7475

7576

77+
class SoftDeprecated(PyVersionChange):
78+
"""Directive for soft deprecations that auto-links to the glossary term.
79+
80+
Usage::
81+
82+
.. soft-deprecated:: 3.15
83+
84+
Use :func:`new_thing` instead.
85+
86+
Renders as: "Soft deprecated since version 3.15: Use new_thing() instead."
87+
with "Soft deprecated" linking to the glossary definition.
88+
"""
89+
90+
def run(self) -> list[Node]:
91+
versionlabels[self.name] = sphinx_gettext(
92+
"Soft deprecated since version %s"
93+
)
94+
versionlabel_classes[self.name] = "softdeprecated"
95+
try:
96+
result = super().run()
97+
finally:
98+
versionlabels[self.name] = ""
99+
versionlabel_classes[self.name] = ""
100+
101+
for node in result:
102+
# Add "versionchanged" class so existing theme CSS applies
103+
node["classes"] = node.get("classes", []) + ["versionchanged"]
104+
# Replace the plain-text "Soft deprecated" with a glossary reference
105+
for inline in node.findall(nodes.inline):
106+
if "versionmodified" in inline.get("classes", []):
107+
self._add_glossary_link(inline)
108+
109+
return result
110+
111+
@staticmethod
112+
def _add_glossary_link(inline: nodes.inline) -> None:
113+
"""Replace 'Soft deprecated' text with a cross-reference to the
114+
:term:`soft deprecated` glossary entry."""
115+
marker = "Soft deprecated"
116+
ref = addnodes.pending_xref(
117+
"",
118+
nodes.Text(marker),
119+
refdomain="std",
120+
reftype="term",
121+
reftarget="soft deprecated",
122+
refwarn=True,
123+
)
124+
125+
for child in inline.children:
126+
if not isinstance(child, nodes.Text):
127+
continue
128+
129+
text = str(child)
130+
idx = text.find(marker)
131+
if idx < 0:
132+
continue
133+
134+
# Replace the text node with the split parts using docutils API
135+
new_nodes: list[nodes.Node] = []
136+
if idx > 0:
137+
new_nodes.append(nodes.Text(text[:idx]))
138+
139+
new_nodes.append(ref)
140+
remainder = text[idx + len(marker) :]
141+
if remainder:
142+
new_nodes.append(nodes.Text(remainder))
143+
144+
child.parent.replace(child, new_nodes)
145+
break
146+
147+
76148
def setup(app: Sphinx) -> ExtensionMetadata:
77149
# Override Sphinx's directives with support for 'next'
78150
app.add_directive("versionadded", PyVersionChange, override=True)
@@ -83,6 +155,9 @@ def setup(app: Sphinx) -> ExtensionMetadata:
83155
# Register the ``.. deprecated-removed::`` directive
84156
app.add_directive("deprecated-removed", DeprecatedRemoved)
85157

158+
# Register the ``.. soft-deprecated::`` directive
159+
app.add_directive("soft-deprecated", SoftDeprecated)
160+
86161
return {
87162
"version": "1.0",
88163
"parallel_read_safe": True,

0 commit comments

Comments
 (0)