diff --git a/CHANGES.rst b/CHANGES.rst index e3eaf731ff7..59b0ef4c2a7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -101,6 +101,9 @@ Features added * #13105: Introduce the :rst:role:`py:deco` role to cross-reference decorator functions and methods in the Python domain. Patch by Adam Turner. +* #9169: Add the :confval:`intersphinx_resolve_self` option + to resolve an intersphinx reference to the current project. + Patch by Jakob Lykke Andersen and Adam Turner. Bugs fixed ---------- diff --git a/doc/usage/extensions/intersphinx.rst b/doc/usage/extensions/intersphinx.rst index 08297d37079..91de6274f36 100644 --- a/doc/usage/extensions/intersphinx.rst +++ b/doc/usage/extensions/intersphinx.rst @@ -133,6 +133,33 @@ linking: ('../../otherbook/build/html/objects.inv', None)), } +.. confval:: intersphinx_resolve_self + :type: :code-py:`str` + :default: :code-py:`''` + + If provided, :confval:`!intersphinx_resolve_self` overrides intersphinx's + resolution mechanism to resolve all references to the current project, + rather than an external reference. + This is useful when documentation is shared between projects, + with the 'upstream' or 'parent' project using intersphinx-style references + in its documentation. + For example, a project such as *Astropy* might set: + + .. code-block:: python + + intersphinx_resolve_self = 'astropy' + + Projects re-using *Astropy*'s documentation or inheriting their docstrings + would then configure their :confval:`!intersphinx_mapping` with + an :code-py:`'astropy'` key, pointing to *astropy*'s :file:`objects.inv`. + For example: + + .. code-block:: python + + intersphinx_mapping = { + 'astropy': ('https://docs.astropy.org/en/stable/', None), + } + .. confval:: intersphinx_cache_limit :type: :code-py:`int` :default: :code-py:`5` (five days) diff --git a/sphinx/ext/intersphinx/__init__.py b/sphinx/ext/intersphinx/__init__.py index ea1b32b1701..b3e5315686f 100644 --- a/sphinx/ext/intersphinx/__init__.py +++ b/sphinx/ext/intersphinx/__init__.py @@ -64,6 +64,7 @@ def setup(app: Sphinx) -> ExtensionMetadata: app.add_config_value('intersphinx_mapping', {}, 'env', types=frozenset({dict})) + app.add_config_value('intersphinx_resolve_self', '', 'env', types=frozenset({str})) app.add_config_value('intersphinx_cache_limit', 5, '', types=frozenset({int})) app.add_config_value( 'intersphinx_timeout', None, '', types=frozenset({int, float, type(None)}) diff --git a/sphinx/ext/intersphinx/_resolve.py b/sphinx/ext/intersphinx/_resolve.py index 44032cf5cf7..4e819f4b720 100644 --- a/sphinx/ext/intersphinx/_resolve.py +++ b/sphinx/ext/intersphinx/_resolve.py @@ -364,11 +364,16 @@ def __init__(self, orig_name: str) -> None: def run(self) -> tuple[list[Node], list[system_message]]: assert self.name == self.orig_name.lower() inventory, name_suffix = self.get_inventory_and_name_suffix(self.orig_name) - if inventory and not inventory_exists(self.env, inventory): - self._emit_warning( - __('inventory for external cross-reference not found: %r'), inventory - ) - return [], [] + resolve_self = self.env.config.intersphinx_resolve_self + self_referential = bool(resolve_self) and resolve_self == inventory + + if not self_referential: + if inventory and not inventory_exists(self.env, inventory): + self._emit_warning( + __('inventory for external cross-reference not found: %r'), + inventory, + ) + return [], [] domain_name, role_name = self._get_domain_role(name_suffix) @@ -453,10 +458,14 @@ def run(self) -> tuple[list[Node], list[system_message]]: self.content, ) - for node in result: - if isinstance(node, pending_xref): - node['intersphinx'] = True - node['inventory'] = inventory + if not self_referential: + # We do the intersphinx resolution by inserting our + # 'intersphinx' and 'inventory' attributes into the nodes. + # Only do this when it is an external reference. + for node in result: + if isinstance(node, pending_xref): + node['intersphinx'] = True + node['inventory'] = inventory return result, messages