from __future__ import annotations
from typing import TYPE_CHECKING, overload
if TYPE_CHECKING:
from collections.abc import Iterable, Iterator, Mapping, Set
from typing import Any, Final, Literal, NoReturn, Self
from docutils import nodes
from sphinx.domains import Domain
from sphinx.domains.c import CDomain
from sphinx.domains.changeset import ChangeSetDomain
from sphinx.domains.citation import CitationDomain
from sphinx.domains.cpp import CPPDomain
from sphinx.domains.index import IndexDomain
from sphinx.domains.javascript import JavaScriptDomain
from sphinx.domains.math import MathDomain
from sphinx.domains.python import PythonDomain
from sphinx.domains.rst import ReSTDomain
from sphinx.domains.std import StandardDomain
from sphinx.environment import BuildEnvironment
from sphinx.ext.duration import DurationDomain
from sphinx.ext.todo import TodoDomain
from sphinx.registry import SphinxComponentRegistry
class _DomainsContainer:
"""Container for domain instances.
This class is private, including its name, constructor, and all methods.
Any or all of these will change without notice or warning in any release.
The public interface is restricted to:
* the``domains.['']`` mapping interface
* the ``domains.`` attributes for core domains.
* the `.get()``, ``.keys()``, ``.items()``, and ``.values()`` methods.
Additionally, this class supports ``iter`` and ``len``,
and provides membership testing via the ``in`` operator.
"""
__slots__ = (
'_domain_instances',
'c_domain',
'changeset_domain',
'citation_domain',
'cpp_domain',
'index_domain',
'javascript_domain',
'math_domain',
'python_domain',
'restructuredtext_domain',
'standard_domain',
)
#: First-party domains in :mod:`sphinx.domains`
_core_domains: Final = frozenset({
'std',
# Language-specific domains
'c',
'cpp',
'js',
'py',
'rst',
# Other core domains
'changeset',
'citation',
'index',
'math',
})
@classmethod
def _from_environment(
cls, env: BuildEnvironment, /, *, registry: SphinxComponentRegistry
) -> Self:
create_domains = registry.create_domains
# Initialise domains
if domains := {domain.name: domain for domain in create_domains(env)}:
return cls(**domains) # type: ignore[arg-type]
return cls._from_environment_default(env=env)
@classmethod
def _from_environment_default(cls, *, env: BuildEnvironment) -> Self:
"""Return a default instance with every domain we require."""
from sphinx.domains.c import CDomain
from sphinx.domains.changeset import ChangeSetDomain
from sphinx.domains.citation import CitationDomain
from sphinx.domains.cpp import CPPDomain
from sphinx.domains.index import IndexDomain
from sphinx.domains.javascript import JavaScriptDomain
from sphinx.domains.math import MathDomain
from sphinx.domains.python import PythonDomain
from sphinx.domains.rst import ReSTDomain
from sphinx.domains.std import StandardDomain
return cls(
c=CDomain(env),
changeset=ChangeSetDomain(env),
citation=CitationDomain(env),
cpp=CPPDomain(env),
index=IndexDomain(env),
js=JavaScriptDomain(env),
math=MathDomain(env),
py=PythonDomain(env),
rst=ReSTDomain(env),
std=StandardDomain(env),
)
def __init__(
self,
*,
c: CDomain,
cpp: CPPDomain,
js: JavaScriptDomain,
py: PythonDomain,
rst: ReSTDomain,
std: StandardDomain,
changeset: ChangeSetDomain,
citation: CitationDomain,
index: IndexDomain,
math: MathDomain,
**domains: Domain,
) -> None:
# All domains, including core.
# Implemented as a dict for backwards compatibility.
self._domain_instances: Mapping[str, Domain] = {
'c': c,
'changeset': changeset,
'citation': citation,
'cpp': cpp,
'index': index,
'js': js,
'math': math,
'py': py,
'rst': rst,
'std': std,
**domains,
}
# Provide typed attributes for the core domains
self.standard_domain: StandardDomain = std
self.c_domain: CDomain = c
self.cpp_domain: CPPDomain = cpp
self.javascript_domain: JavaScriptDomain = js
self.python_domain: PythonDomain = py
self.restructuredtext_domain: ReSTDomain = rst
self.changeset_domain: ChangeSetDomain = changeset
self.citation_domain: CitationDomain = citation
self.index_domain: IndexDomain = index
self.math_domain: MathDomain = math
for domain_name, domain in self._domain_instances.items():
# invariant from ``_DomainsContainer._from_environment``
if domain_name != domain.name:
msg = f'Domain name mismatch in {domain!r}: {domain_name!r} != {domain.name!r}'
raise ValueError(msg)
def _setup(self) -> None:
for domain in self._domain_instances.values():
domain.setup()
def _process_doc(
self, env: BuildEnvironment, docname: str, document: nodes.document
) -> None:
for domain in self._domain_instances.values():
domain.process_doc(env, docname, document)
def _clear_doc(self, docname: str) -> None:
for domain in self._domain_instances.values():
domain.clear_doc(docname)
def _merge_domain_data(
self, docnames: Set[str], domain_data: dict[str, Any]
) -> None:
for domain_name, domain in self._domain_instances.items():
domain.merge_domaindata(docnames, domain_data[domain_name])
def _check_consistency(self) -> None:
for domain in self._domain_instances.values():
domain.check_consistency()
def __contains__(self, key: str) -> bool:
return key in self._domain_instances
def __eq__(self, other: object) -> bool:
if not isinstance(other, _DomainsContainer):
return NotImplemented
return self._domain_instances == other._domain_instances
def __hash__(self) -> int:
return hash(sorted(self._domain_instances.items()))
def __setattr__(self, key: str, value: object) -> None:
if key in self._core_domains:
msg = f'{self.__class__.__name__!r} object does not support assignment to {key!r}'
raise TypeError(msg)
super().__setattr__(key, value)
def __delattr__(self, key: str) -> None:
if key in self._core_domains:
msg = f'{self.__class__.__name__!r} object does not support deletion of {key!r}'
raise TypeError(msg)
super().__delattr__(key)
# Mapping interface: builtin domains
@overload
def __getitem__(self, key: Literal['c']) -> CDomain: ...
@overload
def __getitem__(self, key: Literal['cpp']) -> CPPDomain: ...
@overload
def __getitem__(self, key: Literal['changeset']) -> ChangeSetDomain: ...
@overload
def __getitem__(self, key: Literal['citation']) -> CitationDomain: ...
@overload
def __getitem__(self, key: Literal['index']) -> IndexDomain: ...
@overload
def __getitem__(self, key: Literal['js']) -> JavaScriptDomain: ...
@overload
def __getitem__(self, key: Literal['math']) -> MathDomain: ...
@overload
def __getitem__(self, key: Literal['py']) -> PythonDomain: ...
@overload
def __getitem__(self, key: Literal['rst']) -> ReSTDomain: ...
@overload
def __getitem__(self, key: Literal['std']) -> StandardDomain: ...
# Mapping interface: first-party domains
@overload
def __getitem__(self, key: Literal['duration']) -> DurationDomain: ...
@overload
def __getitem__(self, key: Literal['todo']) -> TodoDomain: ...
# Mapping interface: third-party domains
@overload
def __getitem__(self, key: str) -> Domain: ...
def __getitem__(self, key: str) -> Domain:
if domain := getattr(self, key, None):
return domain
return self._domain_instances[key]
def __setitem__(self, key: str, value: Domain) -> NoReturn:
msg = f'{self.__class__.__name__!r} object does not support item assignment'
raise TypeError(msg)
def __delitem__(self, key: str) -> NoReturn:
msg = f'{self.__class__.__name__!r} object does not support item deletion'
raise TypeError(msg)
def __iter__(self) -> Iterator[str]:
return iter(self._domain_instances.keys())
def __len__(self) -> int:
return len(self._domain_instances)
def get(self, key: str, default: Domain | None = None) -> Domain | None:
return self._domain_instances.get(key, default)
def keys(self) -> Iterable[str]:
return self._domain_instances.keys()
def items(self) -> Iterable[tuple[str, Domain]]:
return self._domain_instances.items()
def values(self) -> Iterable[Domain]:
return self._domain_instances.values()
def sorted(self) -> Iterable[Domain]:
for _domain_name, domain in sorted(self._domain_instances.items()):
yield domain