Skip to content

Commit c1a02f9

Browse files
authored
GH-121970: Extract pydoc_topics into a new extension (#131256)
1 parent 2270684 commit c1a02f9

File tree

4 files changed

+13021
-17646
lines changed

4 files changed

+13021
-17646
lines changed

Diff for: Doc/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
'issue_role',
3434
'lexers',
3535
'misc_news',
36+
'pydoc_topics',
3637
'pyspecific',
3738
'sphinx.ext.coverage',
3839
'sphinx.ext.doctest',

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

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

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
# Used in conf.py and updated here by python/release-tools/run_release.py
3125
SOURCE_URI = 'https://github.com/python/cpython/tree/main/%s'
@@ -57,69 +51,6 @@ def run(self):
5751
return PyMethod.run(self)
5852

5953

60-
# Support for building "topic help" for pydoc
61-
62-
pydoc_topic_labels = [
63-
'assert', 'assignment', 'assignment-expressions', 'async', 'atom-identifiers',
64-
'atom-literals', 'attribute-access', 'attribute-references', 'augassign', 'await',
65-
'binary', 'bitwise', 'bltin-code-objects', 'bltin-ellipsis-object',
66-
'bltin-null-object', 'bltin-type-objects', 'booleans',
67-
'break', 'callable-types', 'calls', 'class', 'comparisons', 'compound',
68-
'context-managers', 'continue', 'conversions', 'customization', 'debugger',
69-
'del', 'dict', 'dynamic-features', 'else', 'exceptions', 'execmodel',
70-
'exprlists', 'floating', 'for', 'formatstrings', 'function', 'global',
71-
'id-classes', 'identifiers', 'if', 'imaginary', 'import', 'in', 'integers',
72-
'lambda', 'lists', 'naming', 'nonlocal', 'numbers', 'numeric-types',
73-
'objects', 'operator-summary', 'pass', 'power', 'raise', 'return',
74-
'sequence-types', 'shifting', 'slicings', 'specialattrs', 'specialnames',
75-
'string-methods', 'strings', 'subscriptions', 'truth', 'try', 'types',
76-
'typesfunctions', 'typesmapping', 'typesmethods', 'typesmodules',
77-
'typesseq', 'typesseq-mutable', 'unary', 'while', 'with', 'yield'
78-
]
79-
80-
81-
class PydocTopicsBuilder(Builder):
82-
name = 'pydoc-topics'
83-
84-
default_translator_class = TextTranslator
85-
86-
def init(self):
87-
self.topics = {}
88-
self.secnumbers = {}
89-
90-
def get_outdated_docs(self):
91-
return 'all pydoc topics'
92-
93-
def get_target_uri(self, docname, typ=None):
94-
return '' # no URIs
95-
96-
def write(self, *ignored):
97-
writer = TextWriter(self)
98-
for label in status_iterator(pydoc_topic_labels,
99-
'building topics... ',
100-
length=len(pydoc_topic_labels)):
101-
if label not in self.env.domaindata['std']['labels']:
102-
self.env.logger.warning(f'label {label!r} not in documentation')
103-
continue
104-
docname, labelid, sectname = self.env.domaindata['std']['labels'][label]
105-
doctree = self.env.get_and_resolve_doctree(docname, self)
106-
document = new_document('<section node>')
107-
document.append(doctree.ids[labelid])
108-
destination = StringOutput(encoding='utf-8')
109-
writer.write(document, destination)
110-
self.topics[label] = writer.output
111-
112-
def finish(self):
113-
f = open(path.join(self.outdir, 'topics.py'), 'wb')
114-
try:
115-
f.write('# -*- coding: utf-8 -*-\n'.encode('utf-8'))
116-
f.write(('# Autogenerated by Sphinx on %s\n' % asctime()).encode('utf-8'))
117-
f.write('# as part of the release process.\n'.encode('utf-8'))
118-
f.write(('topics = ' + pformat(self.topics) + '\n').encode('utf-8'))
119-
finally:
120-
f.close()
121-
122-
12354
# Support for documenting Opcodes
12455

12556
opcode_sig_re = re.compile(r'(\w+(?:\+\d)?)(?:\s*\((.*)\))?')
@@ -196,7 +127,6 @@ def patch_pairindextypes(app, _env) -> None:
196127

197128

198129
def setup(app):
199-
app.add_builder(PydocTopicsBuilder)
200130
app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature)
201131
app.add_object_type('pdbcommand', 'pdbcmd', '%s (pdb command)', parse_pdb_command)
202132
app.add_object_type('monitoring-event', 'monitoring-event', '%s (monitoring event)', parse_monitoring_event)

0 commit comments

Comments
 (0)