From 579632ae131ca2369dde67efb8a267c63bd3554e Mon Sep 17 00:00:00 2001 From: Maciej Olko Date: Sun, 30 Aug 2026 19:09:14 +0200 Subject: [PATCH 01/11] Include generated pages in documentation navigation --- Doc/conf.py | 1 + Doc/tools/extensions/meta_navigation.py | 138 ++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 Doc/tools/extensions/meta_navigation.py diff --git a/Doc/conf.py b/Doc/conf.py index 8791aba435f49ea..3dee8a7b9fe55dd 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -30,6 +30,7 @@ 'implementation_detail', 'issue_role', 'lexers', + 'meta_navigation', 'misc_news', 'profiling_trace', 'pydoc_topics', diff --git a/Doc/tools/extensions/meta_navigation.py b/Doc/tools/extensions/meta_navigation.py new file mode 100644 index 000000000000000..7fcaefa106e5b9f --- /dev/null +++ b/Doc/tools/extensions/meta_navigation.py @@ -0,0 +1,138 @@ +"""Include generated HTML pages in the previous/next navigation flow.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from sphinx.locale import _ as sphinx_gettext + +if TYPE_CHECKING: + from docutils.nodes import document + from sphinx.application import Sphinx + from sphinx.util.typing import ExtensionMetadata + + +def _has_module_index(app: Sphinx) -> bool: + return any( + name == 'py-modindex' for name, *_ in app.builder.domain_indices + ) + + +def _genindex_pages(app: Sphinx, context: dict[str, Any]) -> list[str]: + if not app.builder.use_index: + return [] + + pages = ['genindex'] + if app.config.html_split_index: + pages.extend( + f'genindex-{key}' + for key, _entries in context.get('genindexentries', ()) + ) + pages.append('genindex-all') + return pages + + +def _page_title(app: Sphinx, pagename: str) -> str: + if title_node := app.env.titles.get(pagename): + return app.builder.render_partial(title_node)['title'] + + if pagename == 'py-modindex': + return sphinx_gettext('Python Module Index') + if pagename in {'genindex', 'genindex-all'}: + return sphinx_gettext('Index') + if pagename.startswith('genindex-'): + return ( + f"{sphinx_gettext('Index')} – {pagename.removeprefix('genindex-')}" + ) + if pagename == 'search': + return sphinx_gettext('Search') + if pagename == 'download': + return sphinx_gettext('Download') + raise ValueError(f'unknown generated page: {pagename}') + + +def _splice_flow( + app: Sphinx, + pagename: str, + context: dict[str, Any], + flow: list[str], +) -> None: + try: + position = flow.index(pagename) + except ValueError: + return + + relation_links = { + rellink[2]: rellink + for rellink in context['rellinks'] + if rellink[2] in {'N', 'P'} + } + for offset, direction, accesskey in ( + (1, 'next', 'N'), + (-1, 'prev', 'P'), + ): + target_position = position + offset + if not 0 <= target_position < len(flow): + continue + + target = flow[target_position] + title = _page_title(app, target) + context[direction] = { + 'link': context['pathto'](target), + 'title': title, + } + relation_links[accesskey] = ( + target, + title, + accesskey, + sphinx_gettext(direction), + ) + + context['rellinks'] = [ + rellink + for rellink in context['rellinks'] + if rellink[2] not in {'N', 'P'} + ] + context['rellinks'].extend( + relation_links[accesskey] + for accesskey in ('N', 'P') + if accesskey in relation_links + ) + + +def add_meta_page_relations( + app: Sphinx, + pagename: str, + _templatename: str, + context: dict[str, Any], + _doctree: document | None, +) -> None: + if app.builder.name != 'html': + return + + index_flow = ['glossary'] + if _has_module_index(app): + index_flow.append('py-modindex') + if app.builder.search: + index_flow.append('search') + index_flow.extend(_genindex_pages(app, context)) + index_flow.append('bugs') + _splice_flow(app, pagename, context, index_flow) + + if 'download' in app.config.html_additional_pages: + _splice_flow( + app, + pagename, + context, + ['copyright', 'download', 'about'], + ) + + +def setup(app: Sphinx) -> ExtensionMetadata: + app.connect('html-page-context', add_meta_page_relations) + + return { + 'version': '1.0', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } From 33128797b551d87ffea213fce158064862a7314d Mon Sep 17 00:00:00 2001 From: Maciej Olko Date: Mon, 31 Aug 2026 11:53:24 +0200 Subject: [PATCH 02/11] Add page actions to generated documentation pages --- Doc/tools/extensions/meta_navigation.py | 12 ++++++++++++ Doc/tools/templates/customsourcelink.html | 20 +++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Doc/tools/extensions/meta_navigation.py b/Doc/tools/extensions/meta_navigation.py index 7fcaefa106e5b9f..4df6c79d4a459e9 100644 --- a/Doc/tools/extensions/meta_navigation.py +++ b/Doc/tools/extensions/meta_navigation.py @@ -12,6 +12,12 @@ from sphinx.util.typing import ExtensionMetadata +_TEMPLATE_SOURCES = { + 'download': 'tools/templates/download.html', + 'search': 'tools/templates/search.html', +} + + def _has_module_index(app: Sphinx) -> bool: return any( name == 'py-modindex' for name, *_ in app.builder.domain_indices @@ -110,6 +116,12 @@ def add_meta_page_relations( if app.builder.name != 'html': return + if pagename in _TEMPLATE_SOURCES: + context['show_page_menu'] = True + context['page_source_path'] = _TEMPLATE_SOURCES[pagename] + elif pagename == 'py-modindex' or pagename.startswith('genindex'): + context['show_page_menu'] = True + index_flow = ['glossary'] if _has_module_index(app): index_flow.append('py-modindex') diff --git a/Doc/tools/templates/customsourcelink.html b/Doc/tools/templates/customsourcelink.html index eb194aa038c1bef..810155df181c2ba 100644 --- a/Doc/tools/templates/customsourcelink.html +++ b/Doc/tools/templates/customsourcelink.html @@ -1,25 +1,35 @@ -{%- if page_source_suffix is defined %} +{%- if page_source_path is defined %} + {%- set source_path = page_source_path %} +{%- elif page_source_suffix is defined %} + {%- set source_path = pagename + '.rst' %} +{%- else %} + {%- set source_path = none %} +{%- endif %} +{%- if page_source_suffix is defined or show_page_menu|default(false) %} + {% if source_path %} + {% endif %}

{{ _('This page') }}