diff --git a/pep_sphinx_extensions/__init__.py b/pep_sphinx_extensions/__init__.py index a281d6e6faf..55f5c93689b 100644 --- a/pep_sphinx_extensions/__init__.py +++ b/pep_sphinx_extensions/__init__.py @@ -13,6 +13,7 @@ from pep_sphinx_extensions.pep_processor.html import pep_html_translator from pep_sphinx_extensions.pep_processor.parsing import pep_parser from pep_sphinx_extensions.pep_processor.parsing import pep_role +from pep_sphinx_extensions.pep_processor.transforms import pep_references from pep_sphinx_extensions.pep_zero_generator.pep_index_generator import create_pep_zero if TYPE_CHECKING: @@ -28,6 +29,7 @@ def _depart_maths(): def _update_config_for_builder(app: Sphinx): + app.env.document_ids = {} # For PEPReferenceRoleTitleText if app.builder.name == "dirhtml": app.env.settings["pep_url"] = "../pep-{:0>4}" @@ -49,6 +51,8 @@ def setup(app: Sphinx) -> dict[str, bool]: app.add_role("pep", pep_role.PEPRole(), override=True) # Transform PEP references to links + app.add_post_transform(pep_references.PEPReferenceRoleTitleText) + # Register event callbacks app.connect("builder-inited", _update_config_for_builder) # Update configuration values for builder used app.connect("env-before-read-docs", create_pep_zero) # PEP 0 hook diff --git a/pep_sphinx_extensions/pep_processor/parsing/pep_role.py b/pep_sphinx_extensions/pep_processor/parsing/pep_role.py index e74ab0dd307..18ecdc9829d 100644 --- a/pep_sphinx_extensions/pep_processor/parsing/pep_role.py +++ b/pep_sphinx_extensions/pep_processor/parsing/pep_role.py @@ -1,14 +1,35 @@ +from docutils import nodes from sphinx import roles -class PEPRole(roles.PEP): +class PEPRole(roles.ReferenceRole): """Override the :pep: role""" - # TODO override the entire thing (internal should be True) - def build_uri(self) -> str: - """Get PEP URI from role text.""" + def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]: + # Get PEP URI from role text. pep_str, _, fragment = self.target.partition("#") - pep_base = self.inliner.document.settings.pep_url.format(int(pep_str)) + try: + pep_num = int(pep_str) + except ValueError: + msg = self.inliner.reporter.error(f'invalid PEP number {self.target}', line=self.lineno) + prb = self.inliner.problematic(self.rawtext, self.rawtext, msg) + return [prb], [msg] + pep_base = self.inliner.document.settings.pep_url.format(pep_num) if fragment: - return f"{pep_base}#{fragment}" - return pep_base + ref_uri = f"{pep_base}#{fragment}" + else: + ref_uri = pep_base + if self.has_explicit_title: + title = self.title + else: + title = f"PEP {pep_num}" + + return [ + nodes.reference( + "", title, + internal=True, + refuri=ref_uri, + classes=["pep"], + _title_tuple=(pep_num, fragment) + ) + ], [] diff --git a/pep_sphinx_extensions/pep_processor/transforms/pep_references.py b/pep_sphinx_extensions/pep_processor/transforms/pep_references.py new file mode 100644 index 00000000000..1e00e84cb20 --- /dev/null +++ b/pep_sphinx_extensions/pep_processor/transforms/pep_references.py @@ -0,0 +1,36 @@ +from pathlib import Path + +from docutils import nodes +from docutils import transforms + + +class PEPReferenceRoleTitleText(transforms.Transform): + """Add title text of document titles to reference role references.""" + + default_priority = 730 + + def apply(self) -> None: + if not Path(self.document["source"]).match("pep-*"): + return # not a PEP file, exit early + for node in self.document.findall(nodes.reference): + if "_title_tuple" not in node: + continue + + # get pep number and section target (fragment) + pep_num, fragment = node.attributes.pop("_title_tuple") + filename = f"pep-{pep_num:0>4}" + + # Cache target_ids + env = self.document.settings.env + try: + target_ids = env.document_ids[filename] + except KeyError: + env.document_ids[filename] = target_ids = env.get_doctree(filename).ids + + # Create title text string. We hijack the 'reftitle' attribute so + # that we don't have to change things in the HTML translator + node["reftitle"] = env.titles[filename].astext() + try: + node["reftitle"] += f" ยง {target_ids[fragment][0].astext()}" + except KeyError: + pass