cpython/Doc/tools/extensions/pydoc_topics.py

189 lines
4.7 KiB
Python

"""Support for building "topic help" for pydoc."""
from __future__ import annotations
from time import asctime
from typing import TYPE_CHECKING
from sphinx.builders.text import TextBuilder
from sphinx.util import logging
from sphinx.util.display import status_iterator
from sphinx.util.docutils import new_document
from sphinx.writers.text import TextTranslator
if TYPE_CHECKING:
from collections.abc import Sequence, Set
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata
logger = logging.getLogger(__name__)
_PYDOC_TOPIC_LABELS: Sequence[str] = sorted({
"assert",
"assignment",
"assignment-expressions",
"async",
"atom-identifiers",
"atom-literals",
"attribute-access",
"attribute-references",
"augassign",
"await",
"binary",
"bitwise",
"bltin-code-objects",
"bltin-ellipsis-object",
"bltin-null-object",
"bltin-type-objects",
"booleans",
"break",
"callable-types",
"calls",
"class",
"comparisons",
"compound",
"context-managers",
"continue",
"conversions",
"customization",
"debugger",
"del",
"dict",
"dynamic-features",
"else",
"exceptions",
"execmodel",
"exprlists",
"floating",
"for",
"formatstrings",
"function",
"global",
"id-classes",
"identifiers",
"if",
"imaginary",
"import",
"in",
"integers",
"lambda",
"lists",
"naming",
"nonlocal",
"numbers",
"numeric-types",
"objects",
"operator-summary",
"pass",
"power",
"raise",
"return",
"sequence-types",
"shifting",
"slicings",
"specialattrs",
"specialnames",
"string-methods",
"strings",
"subscriptions",
"truth",
"try",
"types",
"typesfunctions",
"typesmapping",
"typesmethods",
"typesmodules",
"typesseq",
"typesseq-mutable",
"unary",
"while",
"with",
"yield",
})
class PydocTopicsBuilder(TextBuilder):
name = "pydoc-topics"
def init(self) -> None:
super().init()
self.topics: dict[str, str] = {}
def get_outdated_docs(self) -> str:
# Return a string describing what an update build will build.
return "all pydoc topics"
def write_documents(self, _docnames: Set[str]) -> None:
env = self.env
labels: dict[str, tuple[str, str, str]]
labels = env.domains.standard_domain.labels
# docname -> list of (topic_label, label_id) pairs
doc_labels: dict[str, list[tuple[str, str]]] = {}
for topic_label in _PYDOC_TOPIC_LABELS:
try:
docname, label_id, _section_name = labels[topic_label]
except KeyError:
logger.warning("label %r not in documentation", topic_label)
continue
doc_labels.setdefault(docname, []).append((topic_label, label_id))
for docname, label_ids in status_iterator(
doc_labels.items(),
"building topics... ",
length=len(doc_labels),
stringify_func=_display_labels,
):
doctree = env.get_and_resolve_doctree(docname, builder=self)
doc_ids = doctree.ids
for topic_label, label_id in label_ids:
document = new_document("<section node>")
document.append(doc_ids[label_id])
visitor = TextTranslator(document, builder=self)
document.walkabout(visitor)
body = "\n".join(map(str.rstrip, visitor.body.splitlines()))
self.topics[topic_label] = body + "\n"
def finish(self) -> None:
topics_repr = "\n".join(
f" '{topic}': {_repr(self.topics[topic])},"
for topic in sorted(self.topics)
)
topics = f"""\
# Autogenerated by Sphinx on {asctime()}
# as part of the release process.
topics = {{
{topics_repr}
}}
"""
self.outdir.joinpath("topics.py").write_text(topics, encoding="utf-8")
def _display_labels(item: tuple[str, Sequence[tuple[str, str]]]) -> str:
_docname, label_ids = item
labels = [name for name, _id in label_ids]
if len(labels) > 4:
return f"{labels[0]}, {labels[1]}, ..., {labels[-2]}, {labels[-1]}"
return ", ".join(labels)
def _repr(text: str, /) -> str:
"""Return a triple-single-quoted representation of text."""
if "'''" not in text:
return f"r'''{text}'''"
text = text.replace("\\", "\\\\").replace("'''", r"\'\'\'")
return f"'''{text}'''"
def setup(app: Sphinx) -> ExtensionMetadata:
app.add_builder(PydocTopicsBuilder)
return {
"version": "1.0",
"parallel_read_safe": True,
"parallel_write_safe": True,
}