mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Refactor `ReferencesResolver
` (#13321)
Split ``ReferencesResolver.run()`` into smaller parts.
This commit is contained in:
parent
03df8119b3
commit
9eb5097a56
@ -30,7 +30,8 @@ if TYPE_CHECKING:
|
|||||||
InventoryName,
|
InventoryName,
|
||||||
InventoryURI,
|
InventoryURI,
|
||||||
)
|
)
|
||||||
from sphinx.util.typing import Inventory, _Inventory
|
from sphinx.util.inventory import _Inventory
|
||||||
|
from sphinx.util.typing import Inventory
|
||||||
|
|
||||||
|
|
||||||
def validate_intersphinx_mapping(app: Sphinx, config: Config) -> None:
|
def validate_intersphinx_mapping(app: Sphinx, config: Config) -> None:
|
||||||
|
@ -31,7 +31,7 @@ if TYPE_CHECKING:
|
|||||||
from sphinx.environment import BuildEnvironment
|
from sphinx.environment import BuildEnvironment
|
||||||
from sphinx.ext.intersphinx._shared import InventoryName
|
from sphinx.ext.intersphinx._shared import InventoryName
|
||||||
from sphinx.util.inventory import _InventoryItem
|
from sphinx.util.inventory import _InventoryItem
|
||||||
from sphinx.util.typing import RoleFunction, _Inventory
|
from sphinx.util.typing import Inventory, RoleFunction
|
||||||
|
|
||||||
|
|
||||||
def _create_element_from_result(
|
def _create_element_from_result(
|
||||||
@ -75,7 +75,7 @@ def _create_element_from_result(
|
|||||||
|
|
||||||
def _resolve_reference_in_domain_by_target(
|
def _resolve_reference_in_domain_by_target(
|
||||||
inv_name: InventoryName | None,
|
inv_name: InventoryName | None,
|
||||||
inventory: _Inventory,
|
inventory: Inventory,
|
||||||
domain_name: str,
|
domain_name: str,
|
||||||
objtypes: Iterable[str],
|
objtypes: Iterable[str],
|
||||||
target: str,
|
target: str,
|
||||||
@ -139,7 +139,7 @@ def _resolve_reference_in_domain_by_target(
|
|||||||
|
|
||||||
def _resolve_reference_in_domain(
|
def _resolve_reference_in_domain(
|
||||||
inv_name: InventoryName | None,
|
inv_name: InventoryName | None,
|
||||||
inventory: _Inventory,
|
inventory: Inventory,
|
||||||
honor_disabled_refs: bool,
|
honor_disabled_refs: bool,
|
||||||
disabled_reftypes: Set[str],
|
disabled_reftypes: Set[str],
|
||||||
domain: Domain,
|
domain: Domain,
|
||||||
@ -190,7 +190,7 @@ def _resolve_reference_in_domain(
|
|||||||
def _resolve_reference(
|
def _resolve_reference(
|
||||||
inv_name: InventoryName | None,
|
inv_name: InventoryName | None,
|
||||||
domains: _DomainsContainer,
|
domains: _DomainsContainer,
|
||||||
inventory: _Inventory,
|
inventory: Inventory,
|
||||||
honor_disabled_refs: bool,
|
honor_disabled_refs: bool,
|
||||||
disabled_reftypes: Set[str],
|
disabled_reftypes: Set[str],
|
||||||
node: pending_xref,
|
node: pending_xref,
|
||||||
@ -305,7 +305,7 @@ def resolve_reference_detect_inventory(
|
|||||||
|
|
||||||
Resolution is tried first with the target as is in any inventory.
|
Resolution is tried first with the target as is in any inventory.
|
||||||
If this does not succeed, then the target is split by the first ``:``,
|
If this does not succeed, then the target is split by the first ``:``,
|
||||||
to form ``inv_name:newtarget``. If ``inv_name`` is a named inventory, then resolution
|
to form ``inv_name:new_target``. If ``inv_name`` is a named inventory, then resolution
|
||||||
is tried in that inventory with the new target.
|
is tried in that inventory with the new target.
|
||||||
"""
|
"""
|
||||||
# ordinary direct lookup, use data as is
|
# ordinary direct lookup, use data as is
|
||||||
@ -317,10 +317,10 @@ def resolve_reference_detect_inventory(
|
|||||||
target = node['reftarget']
|
target = node['reftarget']
|
||||||
if ':' not in target:
|
if ':' not in target:
|
||||||
return None
|
return None
|
||||||
inv_name, newtarget = target.split(':', 1)
|
inv_name, _, new_target = target.partition(':')
|
||||||
if not inventory_exists(env, inv_name):
|
if not inventory_exists(env, inv_name):
|
||||||
return None
|
return None
|
||||||
node['reftarget'] = newtarget
|
node['reftarget'] = new_target
|
||||||
res_inv = resolve_reference_in_inventory(env, inv_name, node, contnode)
|
res_inv = resolve_reference_in_inventory(env, inv_name, node, contnode)
|
||||||
node['reftarget'] = target
|
node['reftarget'] = target
|
||||||
return res_inv
|
return res_inv
|
||||||
|
@ -72,101 +72,137 @@ class ReferencesResolver(SphinxPostTransform):
|
|||||||
else:
|
else:
|
||||||
contnode = cast('Element', node[0].deepcopy())
|
contnode = cast('Element', node[0].deepcopy())
|
||||||
|
|
||||||
newnode = None
|
new_node = self._resolve_pending_xref(node, contnode)
|
||||||
|
if new_node:
|
||||||
typ = node['reftype']
|
new_nodes: list[Node] = [new_node]
|
||||||
target = node['reftarget']
|
|
||||||
node.setdefault('refdoc', self.env.docname)
|
|
||||||
refdoc = node.get('refdoc')
|
|
||||||
domain = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
if node.get('refdomain', False):
|
|
||||||
# let the domain try to resolve the reference
|
|
||||||
try:
|
|
||||||
domain = self.env.domains[node['refdomain']]
|
|
||||||
except KeyError as exc:
|
|
||||||
raise NoUri(target, typ) from exc
|
|
||||||
newnode = domain.resolve_xref(
|
|
||||||
self.env, refdoc, self.app.builder, typ, target, node, contnode
|
|
||||||
)
|
|
||||||
# really hardwired reference types
|
|
||||||
elif typ == 'any':
|
|
||||||
newnode = self.resolve_anyref(refdoc, node, contnode)
|
|
||||||
# no new node found? try the missing-reference event
|
|
||||||
if newnode is None:
|
|
||||||
newnode = self.app.emit_firstresult(
|
|
||||||
'missing-reference',
|
|
||||||
self.env,
|
|
||||||
node,
|
|
||||||
contnode,
|
|
||||||
allowed_exceptions=(NoUri,),
|
|
||||||
)
|
|
||||||
# still not found? warn if node wishes to be warned about or
|
|
||||||
# we are in nitpicky mode
|
|
||||||
if newnode is None:
|
|
||||||
self.warn_missing_reference(refdoc, typ, target, node, domain)
|
|
||||||
except NoUri:
|
|
||||||
newnode = None
|
|
||||||
|
|
||||||
if newnode:
|
|
||||||
newnodes: list[Node] = [newnode]
|
|
||||||
else:
|
else:
|
||||||
newnodes = [contnode]
|
new_nodes = [contnode]
|
||||||
if newnode is None and isinstance(
|
if new_node is None and isinstance(
|
||||||
node[0], addnodes.pending_xref_condition
|
node[0], addnodes.pending_xref_condition
|
||||||
):
|
):
|
||||||
matched = self.find_pending_xref_condition(node, ('*',))
|
matched = self.find_pending_xref_condition(node, ('*',))
|
||||||
if matched:
|
if matched:
|
||||||
newnodes = matched
|
new_nodes = matched
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
msg = __(
|
||||||
__(
|
'Could not determine the fallback text for the '
|
||||||
'Could not determine the fallback text for the '
|
'cross-reference. Might be a bug.'
|
||||||
'cross-reference. Might be a bug.'
|
|
||||||
),
|
|
||||||
location=node,
|
|
||||||
)
|
)
|
||||||
|
logger.warning(msg, location=node)
|
||||||
|
|
||||||
node.replace_self(newnodes)
|
node.replace_self(new_nodes)
|
||||||
|
|
||||||
def resolve_anyref(
|
def _resolve_pending_xref(
|
||||||
self,
|
self, node: addnodes.pending_xref, contnode: Element
|
||||||
refdoc: str,
|
) -> nodes.reference | None:
|
||||||
node: pending_xref,
|
new_node: nodes.reference | None
|
||||||
contnode: Element,
|
typ = node['reftype']
|
||||||
) -> Element | None:
|
|
||||||
"""Resolve reference generated by the "any" role."""
|
|
||||||
stddomain = self.env.domains.standard_domain
|
|
||||||
target = node['reftarget']
|
target = node['reftarget']
|
||||||
|
ref_doc = node.setdefault('refdoc', self.env.docname)
|
||||||
|
ref_domain = node.get('refdomain', '')
|
||||||
|
domain: Domain | None
|
||||||
|
if ref_domain:
|
||||||
|
try:
|
||||||
|
domain = self.env.domains[ref_domain]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
domain = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_node = self._resolve_pending_xref_in_domain(
|
||||||
|
domain=domain,
|
||||||
|
node=node,
|
||||||
|
contnode=contnode,
|
||||||
|
ref_doc=ref_doc,
|
||||||
|
typ=typ,
|
||||||
|
target=target,
|
||||||
|
)
|
||||||
|
except NoUri:
|
||||||
|
return None
|
||||||
|
if new_node is not None:
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
try:
|
||||||
|
# no new node found? try the missing-reference event
|
||||||
|
new_node = self.app.emit_firstresult(
|
||||||
|
'missing-reference',
|
||||||
|
self.env,
|
||||||
|
node,
|
||||||
|
contnode,
|
||||||
|
allowed_exceptions=(NoUri,),
|
||||||
|
)
|
||||||
|
except NoUri:
|
||||||
|
return None
|
||||||
|
if new_node is not None:
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
# Still not found? Emit a warning if we are in nitpicky mode
|
||||||
|
# or if the node wishes to be warned about.
|
||||||
|
self.warn_missing_reference(ref_doc, typ, target, node, domain)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _resolve_pending_xref_in_domain(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
domain: Domain | None,
|
||||||
|
node: addnodes.pending_xref,
|
||||||
|
contnode: Element,
|
||||||
|
ref_doc: str,
|
||||||
|
typ: str,
|
||||||
|
target: str,
|
||||||
|
) -> nodes.reference | None:
|
||||||
|
# let the domain try to resolve the reference
|
||||||
|
if domain is not None:
|
||||||
|
return domain.resolve_xref(
|
||||||
|
self.env, ref_doc, self.app.builder, typ, target, node, contnode
|
||||||
|
)
|
||||||
|
|
||||||
|
# really hardwired reference types
|
||||||
|
if typ == 'any':
|
||||||
|
return self._resolve_pending_any_xref(
|
||||||
|
node=node, contnode=contnode, ref_doc=ref_doc, target=target
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _resolve_pending_any_xref(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
node: addnodes.pending_xref,
|
||||||
|
contnode: Element,
|
||||||
|
ref_doc: str,
|
||||||
|
target: str,
|
||||||
|
) -> nodes.reference | None:
|
||||||
|
"""Resolve reference generated by the "any" role."""
|
||||||
|
env = self.env
|
||||||
|
builder = self.app.builder
|
||||||
|
domains = env.domains
|
||||||
|
|
||||||
results: list[tuple[str, nodes.reference]] = []
|
results: list[tuple[str, nodes.reference]] = []
|
||||||
# first, try resolving as :doc:
|
# first, try resolving as :doc:
|
||||||
doc_ref = stddomain.resolve_xref(
|
doc_ref = domains.standard_domain.resolve_xref(
|
||||||
self.env, refdoc, self.app.builder, 'doc', target, node, contnode
|
env, ref_doc, builder, 'doc', target, node, contnode
|
||||||
)
|
)
|
||||||
if doc_ref:
|
if doc_ref:
|
||||||
results.append(('doc', doc_ref))
|
results.append(('doc', doc_ref))
|
||||||
# next, do the standard domain (makes this a priority)
|
# next, do the standard domain (makes this a priority)
|
||||||
results.extend(
|
results += domains.standard_domain.resolve_any_xref(
|
||||||
stddomain.resolve_any_xref(
|
env, ref_doc, builder, target, node, contnode
|
||||||
self.env, refdoc, self.app.builder, target, node, contnode
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
for domain in self.env.domains.sorted():
|
for domain in domains.sorted():
|
||||||
if domain.name == 'std':
|
if domain.name == 'std':
|
||||||
continue # we did this one already
|
continue # we did this one already
|
||||||
try:
|
try:
|
||||||
results.extend(
|
results += domain.resolve_any_xref(
|
||||||
domain.resolve_any_xref(
|
env, ref_doc, builder, target, node, contnode
|
||||||
self.env, refdoc, self.app.builder, target, node, contnode
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
# the domain doesn't yet support the new interface
|
# the domain doesn't yet support the new interface
|
||||||
# we have to manually collect possible references (SLOW)
|
# we have to manually collect possible references (SLOW)
|
||||||
for role in domain.roles:
|
for role in domain.roles:
|
||||||
res = domain.resolve_xref(
|
res = domain.resolve_xref(
|
||||||
self.env, refdoc, self.app.builder, role, target, node, contnode
|
env, ref_doc, builder, role, target, node, contnode
|
||||||
)
|
)
|
||||||
if res and len(res) > 0 and isinstance(res[0], nodes.Element):
|
if res and len(res) > 0 and isinstance(res[0], nodes.Element):
|
||||||
results.append((f'{domain.name}:{role}', res))
|
results.append((f'{domain.name}:{role}', res))
|
||||||
@ -180,29 +216,23 @@ class ReferencesResolver(SphinxPostTransform):
|
|||||||
return f':{name}:`{reftitle}`'
|
return f':{name}:`{reftitle}`'
|
||||||
|
|
||||||
candidates = ' or '.join(starmap(stringify, results))
|
candidates = ' or '.join(starmap(stringify, results))
|
||||||
logger.warning(
|
msg = __(
|
||||||
__(
|
"more than one target found for 'any' cross-reference %r: could be %s"
|
||||||
"more than one target found for 'any' cross-"
|
|
||||||
'reference %r: could be %s'
|
|
||||||
),
|
|
||||||
target,
|
|
||||||
candidates,
|
|
||||||
location=node,
|
|
||||||
type='ref',
|
|
||||||
subtype='any',
|
|
||||||
)
|
)
|
||||||
res_role, newnode = results[0]
|
logger.warning(
|
||||||
|
msg, target, candidates, location=node, type='ref', subtype='any'
|
||||||
|
)
|
||||||
|
res_role, new_node = results[0]
|
||||||
# Override "any" class with the actual role type to get the styling
|
# Override "any" class with the actual role type to get the styling
|
||||||
# approximately correct.
|
# approximately correct.
|
||||||
res_domain = res_role.split(':')[0]
|
res_domain = res_role.partition(':')[0]
|
||||||
if (
|
if (
|
||||||
len(newnode) > 0
|
len(new_node) > 0
|
||||||
and isinstance(newnode[0], nodes.Element)
|
and isinstance(new_node[0], nodes.Element)
|
||||||
and newnode[0].get('classes')
|
and new_node[0].get('classes')
|
||||||
):
|
):
|
||||||
newnode[0]['classes'].append(res_domain)
|
new_node[0]['classes'].extend((res_domain, res_role.replace(':', '-')))
|
||||||
newnode[0]['classes'].append(res_role.replace(':', '-'))
|
return new_node
|
||||||
return newnode
|
|
||||||
|
|
||||||
def warn_missing_reference(
|
def warn_missing_reference(
|
||||||
self,
|
self,
|
||||||
|
Loading…
Reference in New Issue
Block a user