summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Lykke Andersen <Jakob@caput.dk>2021-07-16 14:34:35 +0200
committerJakob Lykke Andersen <Jakob@caput.dk>2021-10-31 13:15:47 +0100
commitf22faa7e06dd46483862d1c2cb2895ca519fe6a6 (patch)
tree645eeddc49012380e0aae7ec507cdb5e92a5ef9e
parent961f5af0963055f0206a437765a087a2bf03bcca (diff)
downloadsphinx-git-f22faa7e06dd46483862d1c2cb2895ca519fe6a6.tar.gz
Add intersphinx_disabled_domains
Fixes sphinx-doc/sphinx#2068 Replaces sphinx-doc/sphinx#8981
-rw-r--r--CHANGES5
-rw-r--r--doc/usage/extensions/intersphinx.rst19
-rw-r--r--sphinx/ext/intersphinx.py39
-rw-r--r--sphinx/templates/quickstart/conf.py_t4
-rw-r--r--tests/test_ext_intersphinx.py93
5 files changed, 127 insertions, 33 deletions
diff --git a/CHANGES b/CHANGES
index ffb1cdc47..06c8d7281 100644
--- a/CHANGES
+++ b/CHANGES
@@ -47,6 +47,11 @@ Features added
* #9695: More CSS classes on Javascript domain descriptions
* #9683: Revert the removal of ``add_stylesheet()`` API. It will be kept until
the Sphinx-6.0 release
+* #2068, add :confval:`intersphinx_disabled_domains` for disabling
+ interphinx resolution of cross-references in specific domains when they
+ do not have an explicit inventory specification.
+ ``sphinx-quickstart`` will insert
+ ``intersphinx_disabled_domains = ['std']`` in its generated ``conf.py``.
Bugs fixed
----------
diff --git a/doc/usage/extensions/intersphinx.rst b/doc/usage/extensions/intersphinx.rst
index 478ddb7ae..5f4d1e7c9 100644
--- a/doc/usage/extensions/intersphinx.rst
+++ b/doc/usage/extensions/intersphinx.rst
@@ -148,6 +148,25 @@ linking:
exception is raised if the server has not issued a response for timeout
seconds.
+.. confval:: intersphinx_disabled_domains
+
+ .. versionadded:: 4.2
+
+ A list of strings being the name of a domain, or the special name ``all``.
+ When a cross-reference without an explicit inventory specification is being
+ resolve by intersphinx, skip resolution if either the domain of the
+ cross-reference is in this list or the special name ``all`` is in the list.
+
+ For example, with ``intersphinx_disabled_domains = ['std']`` a cross-reference
+ ``:doc:`installation``` will not be attempted to be resolved by intersphinx, but
+ ``:doc:`otherbook:installation``` will be attempted to be resolved in the
+ inventory named ``otherbook`` in :confval:`intersphinx_mapping`.
+ At the same time, all cross-references generated in, e.g., Python, declarations
+ will still be attempted to be resolved by intersphinx.
+
+ If ``all`` is in the list of domains, then no references without an explicit
+ inventory will be resolved by intersphinx.
+
Showing all links of an Intersphinx mapping file
------------------------------------------------
diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py
index 848a12eb4..7764390ee 100644
--- a/sphinx/ext/intersphinx.py
+++ b/sphinx/ext/intersphinx.py
@@ -351,11 +351,20 @@ def _resolve_reference_in_domain(inv_name: Optional[str], inventory: Inventory,
def _resolve_reference(env: BuildEnvironment, inv_name: Optional[str], inventory: Inventory,
+ honor_disabled_domains: bool,
node: pending_xref, contnode: TextElement) -> Optional[Element]:
- # figure out which object types we should look for
+ # disabling should only be done if no inventory is given
+ honor_disabled_domains = honor_disabled_domains and inv_name is None
+
+ if honor_disabled_domains and 'all' in env.config.intersphinx_disabled_domains:
+ return None
+
typ = node['reftype']
if typ == 'any':
for domain_name, domain in env.domains.items():
+ if honor_disabled_domains \
+ and domain_name in env.config.intersphinx_disabled_domains:
+ continue
objtypes = list(domain.object_types)
res = _resolve_reference_in_domain(inv_name, inventory,
domain, objtypes,
@@ -368,6 +377,9 @@ def _resolve_reference(env: BuildEnvironment, inv_name: Optional[str], inventory
if not domain_name:
# only objects in domains are in the inventory
return None
+ if honor_disabled_domains \
+ and domain_name in env.config.intersphinx_disabled_domains:
+ return None
domain = env.get_domain(domain_name)
objtypes = domain.objtypes_for_role(typ)
if not objtypes:
@@ -383,8 +395,8 @@ def inventory_exists(env: BuildEnvironment, inv_name: str) -> bool:
def resolve_reference_in_inventory(env: BuildEnvironment,
inv_name: str,
- node: pending_xref,
- contnode: TextElement) -> Optional[Element]:
+ node: pending_xref, contnode: TextElement
+ ) -> Optional[Element]:
"""Attempt to resolve a missing reference via intersphinx references.
Resolution is tried in the given inventory with the target as is.
@@ -393,22 +405,26 @@ def resolve_reference_in_inventory(env: BuildEnvironment,
"""
assert inventory_exists(env, inv_name)
return _resolve_reference(env, inv_name, InventoryAdapter(env).named_inventory[inv_name],
- node, contnode)
+ False, node, contnode)
def resolve_reference_any_inventory(env: BuildEnvironment,
- node: pending_xref,
- contnode: TextElement) -> Optional[Element]:
+ honor_disabled_domains: bool,
+ node: pending_xref, contnode: TextElement
+ ) -> Optional[Element]:
"""Attempt to resolve a missing reference via intersphinx references.
Resolution is tried with the target as is in any inventory.
"""
- return _resolve_reference(env, None, InventoryAdapter(env).main_inventory, node, contnode)
+ return _resolve_reference(env, None, InventoryAdapter(env).main_inventory,
+ honor_disabled_domains,
+ node, contnode)
def resolve_reference_detect_inventory(env: BuildEnvironment,
- node: pending_xref,
- contnode: TextElement) -> Optional[Element]:
+ honor_disabled_domains: bool,
+ node: pending_xref, contnode: TextElement
+ ) -> Optional[Element]:
"""Attempt to resolve a missing reference via intersphinx references.
Resolution is tried first with the target as is in any inventory.
@@ -418,7 +434,7 @@ def resolve_reference_detect_inventory(env: BuildEnvironment,
"""
# ordinary direct lookup, use data as is
- res = resolve_reference_any_inventory(env, node, contnode)
+ res = resolve_reference_any_inventory(env, honor_disabled_domains, node, contnode)
if res is not None:
return res
@@ -439,7 +455,7 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref,
contnode: TextElement) -> Optional[Element]:
"""Attempt to resolve a missing reference via intersphinx references."""
- return resolve_reference_detect_inventory(env, node, contnode)
+ return resolve_reference_detect_inventory(env, True, node, contnode)
def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None:
@@ -470,6 +486,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('intersphinx_mapping', {}, True)
app.add_config_value('intersphinx_cache_limit', 5, False)
app.add_config_value('intersphinx_timeout', None, False)
+ app.add_config_value('intersphinx_disabled_domains', [], True)
app.connect('config-inited', normalize_intersphinx_mapping, priority=800)
app.connect('builder-inited', load_mappings)
app.connect('missing-reference', missing_reference)
diff --git a/sphinx/templates/quickstart/conf.py_t b/sphinx/templates/quickstart/conf.py_t
index f1da41c4a..4e37f3130 100644
--- a/sphinx/templates/quickstart/conf.py_t
+++ b/sphinx/templates/quickstart/conf.py_t
@@ -108,6 +108,10 @@ html_static_path = ['{{ dot }}static']
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
}
+
+# Prevent accidental intersphinx resolution for labels, documents, and other
+# basic cross-references.
+intersphinx_disabled_domains = ['std']
{%- endif %}
{%- if 'sphinx.ext.todo' in extensions %}
diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py
index 2f18748dd..6856c6e62 100644
--- a/tests/test_ext_intersphinx.py
+++ b/tests/test_ext_intersphinx.py
@@ -42,6 +42,12 @@ def reference_check(app, *args, **kwds):
return missing_reference(app, app.env, node, contnode)
+def set_config(app, mapping):
+ app.config.intersphinx_mapping = mapping
+ app.config.intersphinx_cache_limit = 0
+ app.config.intersphinx_disabled_domains = []
+
+
@mock.patch('sphinx.ext.intersphinx.InventoryFile')
@mock.patch('sphinx.ext.intersphinx._read_from_url')
def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status, warning):
@@ -90,13 +96,12 @@ def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status,
def test_missing_reference(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
- app.config.intersphinx_mapping = {
+ set_config(app, {
'https://docs.python.org/': inv_file,
'py3k': ('https://docs.python.org/py3k/', inv_file),
'py3krel': ('py3k', inv_file), # relative path
'py3krelparent': ('../../py3k', inv_file), # relative path, parent dir
- }
- app.config.intersphinx_cache_limit = 0
+ })
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -169,10 +174,9 @@ def test_missing_reference(tempdir, app, status, warning):
def test_missing_reference_pydomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
- app.config.intersphinx_mapping = {
+ set_config(app, {
'https://docs.python.org/': inv_file,
- }
- app.config.intersphinx_cache_limit = 0
+ })
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -210,10 +214,9 @@ def test_missing_reference_pydomain(tempdir, app, status, warning):
def test_missing_reference_stddomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
- app.config.intersphinx_mapping = {
+ set_config(app, {
'cmd': ('https://docs.python.org/', inv_file),
- }
- app.config.intersphinx_cache_limit = 0
+ })
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -242,10 +245,9 @@ def test_missing_reference_stddomain(tempdir, app, status, warning):
def test_missing_reference_cppdomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
- app.config.intersphinx_mapping = {
+ set_config(app, {
'https://docs.python.org/': inv_file,
- }
- app.config.intersphinx_cache_limit = 0
+ })
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -269,10 +271,9 @@ def test_missing_reference_cppdomain(tempdir, app, status, warning):
def test_missing_reference_jsdomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
- app.config.intersphinx_mapping = {
+ set_config(app, {
'https://docs.python.org/': inv_file,
- }
- app.config.intersphinx_cache_limit = 0
+ })
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -291,14 +292,63 @@ def test_missing_reference_jsdomain(tempdir, app, status, warning):
assert rn.astext() == 'baz()'
+def test_missing_reference_disabled_domain(tempdir, app, status, warning):
+ inv_file = tempdir / 'inventory'
+ inv_file.write_bytes(inventory_v2)
+ set_config(app, {
+ 'inv': ('https://docs.python.org/', inv_file),
+ })
+
+ # load the inventory and check if it's done correctly
+ normalize_intersphinx_mapping(app, app.config)
+ load_mappings(app)
+
+ def case(std_without, std_with, py_without, py_with):
+ def assert_(rn, expected):
+ if expected is None:
+ assert rn is None
+ else:
+ assert rn.astext() == expected
+
+ kwargs = {}
+
+ node, contnode = fake_node('std', 'doc', 'docname', 'docname', **kwargs)
+ rn = missing_reference(app, app.env, node, contnode)
+ assert_(rn, std_without)
+
+ node, contnode = fake_node('std', 'doc', 'inv:docname', 'docname', **kwargs)
+ rn = missing_reference(app, app.env, node, contnode)
+ assert_(rn, std_with)
+
+ # an arbitrary ref in another domain
+ node, contnode = fake_node('py', 'func', 'module1.func', 'func()', **kwargs)
+ rn = missing_reference(app, app.env, node, contnode)
+ assert_(rn, py_without)
+
+ node, contnode = fake_node('py', 'func', 'inv:module1.func', 'func()', **kwargs)
+ rn = missing_reference(app, app.env, node, contnode)
+ assert_(rn, py_with)
+
+ # the base case, everything should resolve
+ assert app.config.intersphinx_disabled_domains == []
+ case('docname', 'docname', 'func()', 'func()')
+
+ # disabled one domain
+ app.config.intersphinx_disabled_domains = ['std']
+ case(None, 'docname', 'func()', 'func()')
+
+ # disabled all domains
+ app.config.intersphinx_disabled_domains = ['all']
+ case(None, 'docname', None, 'func()')
+
+
@pytest.mark.xfail(os.name != 'posix', reason="Path separator mismatch issue")
def test_inventory_not_having_version(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2_not_having_version)
- app.config.intersphinx_mapping = {
+ set_config(app, {
'https://docs.python.org/': inv_file,
- }
- app.config.intersphinx_cache_limit = 0
+ })
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -318,16 +368,15 @@ def test_load_mappings_warnings(tempdir, app, status, warning):
"""
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
- app.config.intersphinx_mapping = {
+ set_config(app, {
'https://docs.python.org/': inv_file,
'py3k': ('https://docs.python.org/py3k/', inv_file),
'repoze.workflow': ('http://docs.repoze.org/workflow/', inv_file),
'django-taggit': ('http://django-taggit.readthedocs.org/en/latest/',
inv_file),
12345: ('http://www.sphinx-doc.org/en/stable/', inv_file),
- }
+ })
- app.config.intersphinx_cache_limit = 0
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
load_mappings(app)
@@ -337,7 +386,7 @@ def test_load_mappings_warnings(tempdir, app, status, warning):
def test_load_mappings_fallback(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
- app.config.intersphinx_cache_limit = 0
+ set_config(app, {})
# connect to invalid path
app.config.intersphinx_mapping = {