Skip to content

Commit fb073b5

Browse files
authored
[3.12] GH-121970: Extract pydoc_topics into a new extension (#129116) (#130443)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
1 parent 9d663f0 commit fb073b5

File tree

4 files changed

+12661
-17137
lines changed

4 files changed

+12661
-17137
lines changed

Diff for: Doc/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
'implementation_detail',
3232
'lexers',
3333
'misc_news',
34+
'pydoc_topics',
3435
'pyspecific',
3536
'sphinx.ext.coverage',
3637
'sphinx.ext.doctest',

Diff for: Doc/tools/extensions/pydoc_topics.py

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
"""Support for building "topic help" for pydoc."""
2+
3+
from __future__ import annotations
4+
5+
from time import asctime
6+
from typing import TYPE_CHECKING
7+
8+
from sphinx.builders.text import TextBuilder
9+
from sphinx.util import logging
10+
from sphinx.util.display import status_iterator
11+
from sphinx.util.docutils import new_document
12+
from sphinx.writers.text import TextTranslator
13+
14+
if TYPE_CHECKING:
15+
from collections.abc import Sequence, Set
16+
17+
from sphinx.application import Sphinx
18+
from sphinx.util.typing import ExtensionMetadata
19+
20+
logger = logging.getLogger(__name__)
21+
22+
_PYDOC_TOPIC_LABELS: Sequence[str] = sorted({
23+
"assert",
24+
"assignment",
25+
"async",
26+
"atom-identifiers",
27+
"atom-literals",
28+
"attribute-access",
29+
"attribute-references",
30+
"augassign",
31+
"await",
32+
"binary",
33+
"bitwise",
34+
"bltin-code-objects",
35+
"bltin-ellipsis-object",
36+
"bltin-null-object",
37+
"bltin-type-objects",
38+
"booleans",
39+
"break",
40+
"callable-types",
41+
"calls",
42+
"class",
43+
"comparisons",
44+
"compound",
45+
"context-managers",
46+
"continue",
47+
"conversions",
48+
"customization",
49+
"debugger",
50+
"del",
51+
"dict",
52+
"dynamic-features",
53+
"else",
54+
"exceptions",
55+
"execmodel",
56+
"exprlists",
57+
"floating",
58+
"for",
59+
"formatstrings",
60+
"function",
61+
"global",
62+
"id-classes",
63+
"identifiers",
64+
"if",
65+
"imaginary",
66+
"import",
67+
"in",
68+
"integers",
69+
"lambda",
70+
"lists",
71+
"naming",
72+
"nonlocal",
73+
"numbers",
74+
"numeric-types",
75+
"objects",
76+
"operator-summary",
77+
"pass",
78+
"power",
79+
"raise",
80+
"return",
81+
"sequence-types",
82+
"shifting",
83+
"slicings",
84+
"specialattrs",
85+
"specialnames",
86+
"string-methods",
87+
"strings",
88+
"subscriptions",
89+
"truth",
90+
"try",
91+
"types",
92+
"typesfunctions",
93+
"typesmapping",
94+
"typesmethods",
95+
"typesmodules",
96+
"typesseq",
97+
"typesseq-mutable",
98+
"unary",
99+
"while",
100+
"with",
101+
"yield",
102+
})
103+
104+
105+
class PydocTopicsBuilder(TextBuilder):
106+
name = "pydoc-topics"
107+
108+
def init(self) -> None:
109+
super().init()
110+
self.topics: dict[str, str] = {}
111+
112+
def get_outdated_docs(self) -> str:
113+
# Return a string describing what an update build will build.
114+
return "all pydoc topics"
115+
116+
def write_documents(self, _docnames: Set[str]) -> None:
117+
env = self.env
118+
119+
labels: dict[str, tuple[str, str, str]]
120+
labels = env.domains.standard_domain.labels
121+
122+
# docname -> list of (topic_label, label_id) pairs
123+
doc_labels: dict[str, list[tuple[str, str]]] = {}
124+
for topic_label in _PYDOC_TOPIC_LABELS:
125+
try:
126+
docname, label_id, _section_name = labels[topic_label]
127+
except KeyError:
128+
logger.warning("label %r not in documentation", topic_label)
129+
continue
130+
doc_labels.setdefault(docname, []).append((topic_label, label_id))
131+
132+
for docname, label_ids in status_iterator(
133+
doc_labels.items(),
134+
"building topics... ",
135+
length=len(doc_labels),
136+
stringify_func=_display_labels,
137+
):
138+
doctree = env.get_and_resolve_doctree(docname, builder=self)
139+
doc_ids = doctree.ids
140+
for topic_label, label_id in label_ids:
141+
document = new_document("<section node>")
142+
document.append(doc_ids[label_id])
143+
visitor = TextTranslator(document, builder=self)
144+
document.walkabout(visitor)
145+
self.topics[topic_label] = visitor.body
146+
147+
def finish(self) -> None:
148+
topics_repr = "\n".join(
149+
f" '{topic}': {_repr(self.topics[topic])},"
150+
for topic in sorted(self.topics)
151+
)
152+
topics = f"""\
153+
# Autogenerated by Sphinx on {asctime()}
154+
# as part of the release process.
155+
156+
topics = {{
157+
{topics_repr}
158+
}}
159+
"""
160+
self.outdir.joinpath("topics.py").write_text(topics, encoding="utf-8")
161+
162+
163+
def _display_labels(item: tuple[str, Sequence[tuple[str, str]]]) -> str:
164+
_docname, label_ids = item
165+
labels = [name for name, _id in label_ids]
166+
if len(labels) > 4:
167+
return f"{labels[0]}, {labels[1]}, ..., {labels[-2]}, {labels[-1]}"
168+
return ", ".join(labels)
169+
170+
171+
def _repr(text: str, /) -> str:
172+
"""Return a triple-single-quoted representation of text."""
173+
if "'''" not in text:
174+
return f"r'''{text}'''"
175+
text = text.replace("\\", "\\\\").replace("'''", r"\'\'\'")
176+
return f"'''{text}'''"
177+
178+
179+
def setup(app: Sphinx) -> ExtensionMetadata:
180+
app.add_builder(PydocTopicsBuilder)
181+
182+
return {
183+
"version": "1.0",
184+
"parallel_read_safe": True,
185+
"parallel_write_safe": True,
186+
}

Diff for: Doc/tools/extensions/pyspecific.py

+1-71
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,14 @@
1212
import re
1313
import io
1414
from os import getenv, path
15-
from time import asctime
16-
from pprint import pformat
1715

1816
from docutils import nodes
19-
from docutils.io import StringOutput
2017
from docutils.parsers.rst import directives
21-
from docutils.utils import new_document, unescape
18+
from docutils.utils import unescape
2219
from sphinx import addnodes
23-
from sphinx.builders import Builder
2420
from sphinx.domains.python import PyFunction, PyMethod, PyModule
2521
from sphinx.locale import _ as sphinx_gettext
2622
from sphinx.util.docutils import SphinxDirective
27-
from sphinx.writers.text import TextWriter, TextTranslator
28-
from sphinx.util.display import status_iterator
2923

3024

3125
ISSUE_URI = 'https://bugs.python.org/issue?@action=redirect&bpo=%s'
@@ -90,69 +84,6 @@ def run(self):
9084
return PyMethod.run(self)
9185

9286

93-
# Support for building "topic help" for pydoc
94-
95-
pydoc_topic_labels = [
96-
'assert', 'assignment', 'async', 'atom-identifiers', 'atom-literals',
97-
'attribute-access', 'attribute-references', 'augassign', 'await',
98-
'binary', 'bitwise', 'bltin-code-objects', 'bltin-ellipsis-object',
99-
'bltin-null-object', 'bltin-type-objects', 'booleans',
100-
'break', 'callable-types', 'calls', 'class', 'comparisons', 'compound',
101-
'context-managers', 'continue', 'conversions', 'customization', 'debugger',
102-
'del', 'dict', 'dynamic-features', 'else', 'exceptions', 'execmodel',
103-
'exprlists', 'floating', 'for', 'formatstrings', 'function', 'global',
104-
'id-classes', 'identifiers', 'if', 'imaginary', 'import', 'in', 'integers',
105-
'lambda', 'lists', 'naming', 'nonlocal', 'numbers', 'numeric-types',
106-
'objects', 'operator-summary', 'pass', 'power', 'raise', 'return',
107-
'sequence-types', 'shifting', 'slicings', 'specialattrs', 'specialnames',
108-
'string-methods', 'strings', 'subscriptions', 'truth', 'try', 'types',
109-
'typesfunctions', 'typesmapping', 'typesmethods', 'typesmodules',
110-
'typesseq', 'typesseq-mutable', 'unary', 'while', 'with', 'yield'
111-
]
112-
113-
114-
class PydocTopicsBuilder(Builder):
115-
name = 'pydoc-topics'
116-
117-
default_translator_class = TextTranslator
118-
119-
def init(self):
120-
self.topics = {}
121-
self.secnumbers = {}
122-
123-
def get_outdated_docs(self):
124-
return 'all pydoc topics'
125-
126-
def get_target_uri(self, docname, typ=None):
127-
return '' # no URIs
128-
129-
def write(self, *ignored):
130-
writer = TextWriter(self)
131-
for label in status_iterator(pydoc_topic_labels,
132-
'building topics... ',
133-
length=len(pydoc_topic_labels)):
134-
if label not in self.env.domaindata['std']['labels']:
135-
self.env.logger.warning(f'label {label!r} not in documentation')
136-
continue
137-
docname, labelid, sectname = self.env.domaindata['std']['labels'][label]
138-
doctree = self.env.get_and_resolve_doctree(docname, self)
139-
document = new_document('<section node>')
140-
document.append(doctree.ids[labelid])
141-
destination = StringOutput(encoding='utf-8')
142-
writer.write(document, destination)
143-
self.topics[label] = writer.output
144-
145-
def finish(self):
146-
f = open(path.join(self.outdir, 'topics.py'), 'wb')
147-
try:
148-
f.write('# -*- coding: utf-8 -*-\n'.encode('utf-8'))
149-
f.write(('# Autogenerated by Sphinx on %s\n' % asctime()).encode('utf-8'))
150-
f.write('# as part of the release process.\n'.encode('utf-8'))
151-
f.write(('topics = ' + pformat(self.topics) + '\n').encode('utf-8'))
152-
finally:
153-
f.close()
154-
155-
15687
# Support for documenting Opcodes
15788

15889
opcode_sig_re = re.compile(r'(\w+(?:\+\d)?)(?:\s*\((.*)\))?')
@@ -231,7 +162,6 @@ def patch_pairindextypes(app, _env) -> None:
231162
def setup(app):
232163
app.add_role('issue', issue_role)
233164
app.add_role('gh', gh_issue_role)
234-
app.add_builder(PydocTopicsBuilder)
235165
app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature)
236166
app.add_object_type('pdbcommand', 'pdbcmd', '%s (pdb command)', parse_pdb_command)
237167
app.add_object_type('monitoring-event', 'monitoring-event', '%s (monitoring event)', parse_monitoring_event)

0 commit comments

Comments
 (0)