diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index be8bf0ccb2e..2ea8cd4f8ff 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -969,26 +969,24 @@ def has_wildcard(pattern: str) -> bool: # user sidebar settings html_sidebars = self.get_builder_config('sidebars', 'html') - for pattern, patsidebars in html_sidebars.items(): + msg = __('page %s matches two patterns in html_sidebars: %r and %r') + for pattern, pat_sidebars in html_sidebars.items(): if patmatch(pagename, pattern): - if matched: - if has_wildcard(pattern): - # warn if both patterns contain wildcards - if has_wildcard(matched): - logger.warning(__('page %s matches two patterns in ' - 'html_sidebars: %r and %r'), - pagename, matched, pattern) - # else the already matched pattern is more specific - # than the present one, because it contains no wildcard - continue + if matched and has_wildcard(pattern): + # warn if both patterns contain wildcards + if has_wildcard(matched): + logger.warning(msg, pagename, matched) + # else the already matched pattern is more specific + # than the present one, because it contains no wildcard + continue matched = pattern - sidebars = patsidebars + sidebars = pat_sidebars if len(sidebars) == 0: # keep defaults pass - ctx['sidebars'] = sidebars + ctx['sidebars'] = list(sidebars) # --------- these are overwritten by the serialization builder diff --git a/sphinx/domains/python/_object.py b/sphinx/domains/python/_object.py index 41f9df1986c..b9ee24d36e0 100644 --- a/sphinx/domains/python/_object.py +++ b/sphinx/domains/python/_object.py @@ -89,6 +89,10 @@ def make_xref( return result + _delimiters_re = re.compile( + r'(\s*[\[\]\(\),](?:\s*o[rf]\s)?\s*|\s+o[rf]\s+|\s*\|\s*|\.\.\.)' + ) + def make_xrefs( self, rolename: str, @@ -100,9 +104,7 @@ def make_xrefs( inliner: Inliner | None = None, location: Node | None = None, ) -> list[Node]: - delims = r'(\s*[\[\]\(\),](?:\s*o[rf]\s)?\s*|\s+o[rf]\s+|\s*\|\s*|\.\.\.)' - delims_re = re.compile(delims) - sub_targets = re.split(delims, target) + sub_targets = self._delimiters_re.split(target) split_contnode = bool(contnode and contnode.astext() == target) @@ -112,13 +114,13 @@ def make_xrefs( if split_contnode: contnode = nodes.Text(sub_target) - if in_literal or delims_re.match(sub_target): + if in_literal or self._delimiters_re.match(sub_target): results.append(contnode or innernode(sub_target, sub_target)) # type: ignore[call-arg] else: results.append(self.make_xref(rolename, domain, sub_target, innernode, contnode, env, inliner, location)) - if sub_target in ('Literal', 'typing.Literal', '~typing.Literal'): + if sub_target in {'Literal', 'typing.Literal', '~typing.Literal'}: in_literal = True return results diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index d6582f4cb23..12ca03a597e 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -22,8 +22,10 @@ if TYPE_CHECKING: from collections.abc import Iterator + from typing import Literal from docutils.nodes import Node, Text + from typing_extensions import TypeAlias, TypeIs from sphinx.application import Sphinx from sphinx.config import Config @@ -31,15 +33,22 @@ from sphinx.environment import BuildEnvironment from sphinx.util.typing import ExtensionMetadata + _DEFAULT_SUBSTITUTION_NAMES: TypeAlias = Literal[ + 'version', + 'release', + 'today', + 'translation progress', + ] + logger = logging.getLogger(__name__) -default_substitutions = { +_DEFAULT_SUBSTITUTIONS = frozenset({ 'version', 'release', 'today', 'translation progress', -} +}) class SphinxTransform(Transform): @@ -105,20 +114,25 @@ class DefaultSubstitutions(SphinxTransform): def apply(self, **kwargs: Any) -> None: # only handle those not otherwise defined in the document - to_handle = default_substitutions - set(self.document.substitution_defs) + to_handle = _DEFAULT_SUBSTITUTIONS - set(self.document.substitution_defs) for ref in self.document.findall(nodes.substitution_reference): - refname = ref['refname'] - if refname in to_handle: - if refname == 'translation progress': - # special handling: calculate translation progress - text = _calculate_translation_progress(self.document) - else: - text = self.config[refname] - if refname == 'today' and not text: - # special handling: can also specify a strftime format - text = format_date(self.config.today_fmt or _('%b %d, %Y'), - language=self.config.language) - ref.replace_self(nodes.Text(text)) + if (name := ref['refname']) in to_handle: + ref.replace_self(self._handle_default_substitution(name)) + + def _handle_default_substitution(self, name: _DEFAULT_SUBSTITUTION_NAMES) -> nodes.Text: + if name == 'translation progress': + # special handling: calculate translation progress + return nodes.Text(_calculate_translation_progress(self.document)) + if name == 'today': + if text := self.config.today: + return nodes.Text(text) + # special handling: can also specify a strftime format + return nodes.Text(format_date( + self.config.today_fmt or _('%b %d, %Y'), + language=self.config.language, + )) + # config.version and config.release + return nodes.Text(getattr(self.config, name)) def _calculate_translation_progress(document: nodes.document) -> str: @@ -263,15 +277,15 @@ class ExtraTranslatableNodes(SphinxTransform): default_priority = 10 def apply(self, **kwargs: Any) -> None: - targets = self.config.gettext_additional_targets - target_nodes = [v for k, v in TRANSLATABLE_NODES.items() if k in targets] + targets = frozenset(self.config.gettext_additional_targets) + target_nodes = tuple(v for k, v in TRANSLATABLE_NODES.items() if k in targets) if not target_nodes: return - def is_translatable_node(node: Node) -> bool: - return isinstance(node, tuple(target_nodes)) + def is_translatable_node(node: Node) -> TypeIs[nodes.Element]: + return isinstance(node, target_nodes) - for node in self.document.findall(is_translatable_node): # type: nodes.Element + for node in self.document.findall(is_translatable_node): node['translatable'] = True