"""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("
") 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, }