Skip to content

Commit

Permalink
Generalize to disable specific refs as well.
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobandersen committed Oct 30, 2021
1 parent c3c2b57 commit df78eb2
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 40 deletions.
5 changes: 5 additions & 0 deletions CHANGES
Expand Up @@ -50,6 +50,11 @@ Features added
* #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.
* #2068, add :confval:`intersphinx_disabled_refs` for disabling
interphinx resolution of cross-references that do not have an explicit
inventory specification. Specific types of cross-references can be disabled,
e.g., ``std:doc`` or all cross-references in a specific domain,
e.g., ``std``.

Bugs fixed
----------
Expand Down
31 changes: 19 additions & 12 deletions doc/usage/extensions/intersphinx.rst
Expand Up @@ -148,21 +148,28 @@ linking:
exception is raised if the server has not issued a response for timeout
seconds.

.. confval:: intersphinx_disabled_domains
.. confval:: intersphinx_disabled_refs

.. versionadded:: 4.2
.. versionadded:: 4.3

A list of strings being either:

- the name of a specific reference type,
e.g., ``std:doc``, ``py:func``, or ``cpp:class``,
- the name of a whole domain, e.g., ``std``, ``py``, or ``cpp``, or
- the special name ``all``.

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
resolved 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.
resolved by intersphinx, skip resolution it matches one of the
specifications in this list.

For example, with ``intersphinx_disabled_refs = ['std:doc']``
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.
Expand Down
39 changes: 24 additions & 15 deletions sphinx/ext/intersphinx.py
Expand Up @@ -321,7 +321,9 @@ def _resolve_reference_in_domain_by_target(
return None


def _resolve_reference_in_domain(inv_name: Optional[str], inventory: Inventory,
def _resolve_reference_in_domain(env: BuildEnvironment,
inv_name: Optional[str], inventory: Inventory,
honor_disabled_refs: bool,
domain: Domain, objtypes: List[str],
node: pending_xref, contnode: TextElement
) -> Optional[Element]:
Expand All @@ -336,6 +338,11 @@ def _resolve_reference_in_domain(inv_name: Optional[str], inventory: Inventory,
# the inventory contains domain:type as objtype
objtypes = ["{}:{}".format(domain.name, t) for t in objtypes]

# now that the objtypes list is complete we can remove the disabled ones
if honor_disabled_refs:
disabled = env.config.intersphinx_disabled_refs
objtypes = [o for o in objtypes if o not in disabled]

# without qualification
res = _resolve_reference_in_domain_by_target(inv_name, inventory, domain, objtypes,
node['reftarget'], node, contnode)
Expand All @@ -351,22 +358,23 @@ 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,
honor_disabled_refs: bool,
node: pending_xref, contnode: TextElement) -> Optional[Element]:
# disabling should only be done if no inventory is given
honor_disabled_domains = honor_disabled_domains and inv_name is None
honor_disabled_refs = honor_disabled_refs and inv_name is None

if honor_disabled_domains and 'all' in env.config.intersphinx_disabled_domains:
if honor_disabled_refs and 'all' in env.config.intersphinx_disabled_refs:
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:
if honor_disabled_refs \
and domain_name in env.config.intersphinx_disabled_refs:
continue
objtypes = list(domain.object_types)
res = _resolve_reference_in_domain(inv_name, inventory,
res = _resolve_reference_in_domain(env, inv_name, inventory,
honor_disabled_refs,
domain, objtypes,
node, contnode)
if res is not None:
Expand All @@ -377,14 +385,15 @@ 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:
if honor_disabled_refs \
and domain_name in env.config.intersphinx_disabled_refs:
return None
domain = env.get_domain(domain_name)
objtypes = domain.objtypes_for_role(typ)
if not objtypes:
return None
return _resolve_reference_in_domain(inv_name, inventory,
return _resolve_reference_in_domain(env, inv_name, inventory,
honor_disabled_refs,
domain, objtypes,
node, contnode)

Expand All @@ -409,20 +418,20 @@ def resolve_reference_in_inventory(env: BuildEnvironment,


def resolve_reference_any_inventory(env: BuildEnvironment,
honor_disabled_domains: bool,
honor_disabled_refs: 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,
honor_disabled_domains,
honor_disabled_refs,
node, contnode)


def resolve_reference_detect_inventory(env: BuildEnvironment,
honor_disabled_domains: bool,
honor_disabled_refs: bool,
node: pending_xref, contnode: TextElement
) -> Optional[Element]:
"""Attempt to resolve a missing reference via intersphinx references.
Expand All @@ -434,7 +443,7 @@ def resolve_reference_detect_inventory(env: BuildEnvironment,
"""

# ordinary direct lookup, use data as is
res = resolve_reference_any_inventory(env, honor_disabled_domains, node, contnode)
res = resolve_reference_any_inventory(env, honor_disabled_refs, node, contnode)
if res is not None:
return res

Expand Down Expand Up @@ -486,7 +495,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.add_config_value('intersphinx_disabled_refs', [], True)
app.connect('config-inited', normalize_intersphinx_mapping, priority=800)
app.connect('builder-inited', load_mappings)
app.connect('missing-reference', missing_reference)
Expand Down
38 changes: 25 additions & 13 deletions tests/test_ext_intersphinx.py
Expand Up @@ -45,7 +45,7 @@ def reference_check(app, *args, **kwds):
def set_config(app, mapping):
app.config.intersphinx_mapping = mapping
app.config.intersphinx_cache_limit = 0
app.config.intersphinx_disabled_domains = []
app.config.intersphinx_disabled_refs = []


@mock.patch('sphinx.ext.intersphinx.InventoryFile')
Expand Down Expand Up @@ -303,7 +303,7 @@ def test_missing_reference_disabled_domain(tempdir, app, status, warning):
normalize_intersphinx_mapping(app, app.config)
load_mappings(app)

def case(std_without, std_with, py_without, py_with):
def case(*, term, doc, py):
def assert_(rn, expected):
if expected is None:
assert rn is None
Expand All @@ -312,34 +312,46 @@ def assert_(rn, expected):

kwargs = {}

node, contnode = fake_node('std', 'term', 'a term', 'a term', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, 'a term' if term else None)

node, contnode = fake_node('std', 'term', 'inv:a term', 'a term', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, 'a term')

node, contnode = fake_node('std', 'doc', 'docname', 'docname', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, std_without)
assert_(rn, 'docname' if doc else None)

node, contnode = fake_node('std', 'doc', 'inv:docname', 'docname', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, std_with)
assert_(rn, 'docname')

# 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)
assert_(rn, 'func()' if py else None)

node, contnode = fake_node('py', 'func', 'inv:module1.func', 'func()', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, py_with)
assert_(rn, 'func()')

# the base case, everything should resolve
assert app.config.intersphinx_disabled_domains == []
case('docname', 'docname', 'func()', 'func()')
assert app.config.intersphinx_disabled_refs == []
case(term=True, doc=True, py=True)

# disabled a single ref type
app.config.intersphinx_disabled_refs = ['std:doc']
case(term=True, doc=False, py=True)

# disabled one domain
app.config.intersphinx_disabled_domains = ['std']
case(None, 'docname', 'func()', 'func()')
# disabled a whole domain
app.config.intersphinx_disabled_refs = ['std']
case(term=False, doc=False, py=True)

# disabled all domains
app.config.intersphinx_disabled_domains = ['all']
case(None, 'docname', None, 'func()')
app.config.intersphinx_disabled_refs = ['all']
case(term=False, doc=False, py=False)


@pytest.mark.xfail(os.name != 'posix', reason="Path separator mismatch issue")
Expand Down

0 comments on commit df78eb2

Please sign in to comment.