diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index c17be8bd4e9..4af4d2c43eb 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -22,12 +22,20 @@ if TYPE_CHECKING: from collections.abc import Set - from typing import Any + from typing import Any, Literal from sphinx.application import Sphinx + from sphinx.builders.html._ctx import _GlobalContextHTML from sphinx.config import Config from sphinx.util.typing import ExtensionMetadata + class _GlobalContextEpub3(_GlobalContextHTML): + theme_writing_mode: str | None + html_tag: str + use_meta_charset: bool + skip_ua_compatible: Literal[True] + + logger = logging.getLogger(__name__) @@ -89,6 +97,8 @@ class Epub3Builder(_epub_base.EpubBuilder): html_tag = HTML_TAG use_meta_charset = True + globalcontext: _GlobalContextEpub3 + # Finish by building the epub file def handle_finish(self) -> None: """Create the metainfo files and finally the epub.""" diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 8cf0340100f..5a4b41419e3 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -70,6 +70,7 @@ from docutils.nodes import Node from sphinx.application import Sphinx + from sphinx.builders.html._ctx import _GlobalContextHTML, _PageContextHTML from sphinx.config import Config from sphinx.environment import BuildEnvironment from sphinx.util.typing import ExtensionMetadata @@ -137,6 +138,8 @@ class StandaloneHTMLBuilder(Builder): imgpath: str = '' domain_indices: list[DOMAIN_INDEX_TYPE] = [] + globalcontext: _GlobalContextHTML + def __init__(self, app: Sphinx, env: BuildEnvironment) -> None: super().__init__(app, env) @@ -565,7 +568,7 @@ def prepare_writing(self, docnames: Set[str]) -> None: 'html5_doctype': True, } if self.theme: - self.globalcontext |= { + self.globalcontext |= { # type: ignore[typeddict-item] f'theme_{key}': val for key, val in self.theme.get_options(self.theme_options).items() } @@ -580,7 +583,7 @@ def get_doc_context(self, docname: str, body: str, metatags: str) -> dict[str, A # find out relations prev = next = None parents = [] - rellinks = self.globalcontext['rellinks'][:] + rellinks = list(self.globalcontext['rellinks']) related = self.relations.get(docname) titles = self.env.titles if related and related[2]: @@ -921,7 +924,7 @@ def copy_static_files(self) -> None: self._static_dir.mkdir(parents=True, exist_ok=True) # prepare context for templates - context = self.globalcontext.copy() + context: dict[str, Any] = self.globalcontext.copy() # type: ignore[assignment] if self.indexer is not None: context.update(self.indexer.context_for_searchtool()) @@ -1040,7 +1043,7 @@ def get_output_path(self, page_name: str, /) -> Path: def get_outfilename(self, pagename: str) -> _StrPath: return _StrPath(self.get_output_path(pagename)) - def add_sidebars(self, pagename: str, ctx: dict[str, Any]) -> None: + def add_sidebars(self, pagename: str, ctx: _PageContextHTML) -> None: def has_wildcard(pattern: str) -> bool: return any(char in pattern for char in '*?[') @@ -1080,7 +1083,7 @@ def handle_page( outfilename: Path | None = None, event_arg: Any = None, ) -> None: - ctx = self.globalcontext.copy() + ctx: _PageContextHTML = self.globalcontext.copy() # type: ignore[assignment] # current_page_name is backwards compatibility ctx['pagename'] = ctx['current_page_name'] = pagename ctx['encoding'] = self.config.html_output_encoding @@ -1124,7 +1127,7 @@ def hasdoc(name: str) -> bool: ctx['toctree'] = lambda **kwargs: self._get_local_toctree(pagename, **kwargs) self.add_sidebars(pagename, ctx) - ctx.update(addctx) + ctx.update(addctx) # type: ignore[typeddict-item] # 'blah.html' should have content_root = './' not ''. ctx['content_root'] = (f'..{SEP}' * default_baseuri.count(SEP)) or f'.{SEP}' @@ -1261,7 +1264,7 @@ def js_tag(js: _JavaScript | str) -> str: copyfile(self.env.doc2path(pagename), source_file_path, force=True) def update_page_context( - self, pagename: str, templatename: str, ctx: dict[str, Any], event_arg: Any + self, pagename: str, templatename: str, ctx: _PageContextHTML, event_arg: Any ) -> None: pass diff --git a/sphinx/builders/html/_ctx.py b/sphinx/builders/html/_ctx.py new file mode 100644 index 00000000000..6adbba884b1 --- /dev/null +++ b/sphinx/builders/html/_ctx.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + from typing import Any, Literal, Protocol, TypedDict + + from sphinx.builders.html._assets import _CascadingStyleSheet, _JavaScript + + class _NavigationRelation(TypedDict): + link: str + title: str + + class _GlobalContextHTML(TypedDict): + embedded: bool + project: str + release: str + version: str + last_updated: str | None + copyright: str + master_doc: str + root_doc: str + use_opensearch: bool + docstitle: str | None + shorttitle: str + show_copyright: bool + show_search_summary: bool + show_sphinx: bool + has_source: bool + show_source: bool + sourcelink_suffix: str + file_suffix: str + link_suffix: str + script_files: Sequence[_JavaScript] + language: str | None + css_files: Sequence[_CascadingStyleSheet] + sphinx_version: str + sphinx_version_tuple: tuple[int, int, int, str, int] + docutils_version_info: tuple[int, int, int, str, int] + styles: Sequence[str] + rellinks: Sequence[tuple[str, str, str, str]] + builder: str + parents: Sequence[_NavigationRelation] + logo_url: str + logo_alt: str + favicon_url: str + html5_doctype: Literal[True] + + class _PathtoCallable(Protocol): + def __call__( + self, otheruri: str, resource: bool = False, baseuri: str = ... + ) -> str: ... + + class _ToctreeCallable(Protocol): + def __call__(self, **kwargs: Any) -> str: ... + + class _PageContextHTML(_GlobalContextHTML): + # get_doc_context() + prev: Sequence[_NavigationRelation] + next: Sequence[_NavigationRelation] + title: str + meta: dict[str, Any] | None + body: str + metatags: str + sourcename: str + toc: str + display_toc: bool + page_source_suffix: str + + # handle_page() + pagename: str + current_page_name: str + encoding: str + pageurl: str | None + pathto: _PathtoCallable + hasdoc: Callable[[str], bool] + toctree: _ToctreeCallable + content_root: str + css_tag: Callable[[_CascadingStyleSheet], str] + js_tag: Callable[[_JavaScript], str] + + # add_sidebars() + sidebars: Sequence[str] | None + +else: + _NavigationRelation = dict + _GlobalContextHTML = dict + _PageContextHTML = dict diff --git a/tests/test_builders/test_build_html.py b/tests/test_builders/test_build_html.py index 3ba9c929cc9..1577bacd311 100644 --- a/tests/test_builders/test_build_html.py +++ b/tests/test_builders/test_build_html.py @@ -30,8 +30,7 @@ from tests.test_builders.xpath_util import check_xpath if TYPE_CHECKING: - from typing import Any - + from sphinx.builders.html._ctx import _PageContextHTML from sphinx.testing.util import SphinxTestApp @@ -416,7 +415,7 @@ def test_html_style(app: SphinxTestApp) -> None: }, ) def test_html_sidebar(app: SphinxTestApp) -> None: - ctx: dict[str, Any] = {} + ctx: _PageContextHTML = {} # type: ignore[typeddict-item] # default for alabaster app.build(force_all=True)