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,
|
||||
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:
|
||||
|
@ -31,7 +31,7 @@ if TYPE_CHECKING:
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.ext.intersphinx._shared import InventoryName
|
||||
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(
|
||||
@ -75,7 +75,7 @@ def _create_element_from_result(
|
||||
|
||||
def _resolve_reference_in_domain_by_target(
|
||||
inv_name: InventoryName | None,
|
||||
inventory: _Inventory,
|
||||
inventory: Inventory,
|
||||
domain_name: str,
|
||||
objtypes: Iterable[str],
|
||||
target: str,
|
||||
@ -139,7 +139,7 @@ def _resolve_reference_in_domain_by_target(
|
||||
|
||||
def _resolve_reference_in_domain(
|
||||
inv_name: InventoryName | None,
|
||||
inventory: _Inventory,
|
||||
inventory: Inventory,
|
||||
honor_disabled_refs: bool,
|
||||
disabled_reftypes: Set[str],
|
||||
domain: Domain,
|
||||
@ -190,7 +190,7 @@ def _resolve_reference_in_domain(
|
||||
def _resolve_reference(
|
||||
inv_name: InventoryName | None,
|
||||
domains: _DomainsContainer,
|
||||
inventory: _Inventory,
|
||||
inventory: Inventory,
|
||||
honor_disabled_refs: bool,
|
||||
disabled_reftypes: Set[str],
|
||||
node: pending_xref,
|
||||
@ -305,7 +305,7 @@ def resolve_reference_detect_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 ``:``,
|
||||
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.
|
||||
"""
|
||||
# ordinary direct lookup, use data as is
|
||||
@ -317,10 +317,10 @@ def resolve_reference_detect_inventory(
|
||||
target = node['reftarget']
|
||||
if ':' not in target:
|
||||
return None
|
||||
inv_name, newtarget = target.split(':', 1)
|
||||
inv_name, _, new_target = target.partition(':')
|
||||
if not inventory_exists(env, inv_name):
|
||||
return None
|
||||
node['reftarget'] = newtarget
|
||||
node['reftarget'] = new_target
|
||||
res_inv = resolve_reference_in_inventory(env, inv_name, node, contnode)
|
||||
node['reftarget'] = target
|
||||
return res_inv
|
||||
|
@ -72,101 +72,137 @@ class ReferencesResolver(SphinxPostTransform):
|
||||
else:
|
||||
contnode = cast('Element', node[0].deepcopy())
|
||||
|
||||
newnode = None
|
||||
|
||||
typ = node['reftype']
|
||||
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]
|
||||
new_node = self._resolve_pending_xref(node, contnode)
|
||||
if new_node:
|
||||
new_nodes: list[Node] = [new_node]
|
||||
else:
|
||||
newnodes = [contnode]
|
||||
if newnode is None and isinstance(
|
||||
new_nodes = [contnode]
|
||||
if new_node is None and isinstance(
|
||||
node[0], addnodes.pending_xref_condition
|
||||
):
|
||||
matched = self.find_pending_xref_condition(node, ('*',))
|
||||
if matched:
|
||||
newnodes = matched
|
||||
new_nodes = matched
|
||||
else:
|
||||
logger.warning(
|
||||
__(
|
||||
'Could not determine the fallback text for the '
|
||||
'cross-reference. Might be a bug.'
|
||||
),
|
||||
location=node,
|
||||
msg = __(
|
||||
'Could not determine the fallback text for the '
|
||||
'cross-reference. Might be a bug.'
|
||||
)
|
||||
logger.warning(msg, location=node)
|
||||
|
||||
node.replace_self(newnodes)
|
||||
node.replace_self(new_nodes)
|
||||
|
||||
def resolve_anyref(
|
||||
self,
|
||||
refdoc: str,
|
||||
node: pending_xref,
|
||||
contnode: Element,
|
||||
) -> Element | None:
|
||||
"""Resolve reference generated by the "any" role."""
|
||||
stddomain = self.env.domains.standard_domain
|
||||
def _resolve_pending_xref(
|
||||
self, node: addnodes.pending_xref, contnode: Element
|
||||
) -> nodes.reference | None:
|
||||
new_node: nodes.reference | None
|
||||
typ = node['reftype']
|
||||
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]] = []
|
||||
# first, try resolving as :doc:
|
||||
doc_ref = stddomain.resolve_xref(
|
||||
self.env, refdoc, self.app.builder, 'doc', target, node, contnode
|
||||
doc_ref = domains.standard_domain.resolve_xref(
|
||||
env, ref_doc, builder, 'doc', target, node, contnode
|
||||
)
|
||||
if doc_ref:
|
||||
results.append(('doc', doc_ref))
|
||||
# next, do the standard domain (makes this a priority)
|
||||
results.extend(
|
||||
stddomain.resolve_any_xref(
|
||||
self.env, refdoc, self.app.builder, target, node, contnode
|
||||
)
|
||||
results += domains.standard_domain.resolve_any_xref(
|
||||
env, ref_doc, builder, target, node, contnode
|
||||
)
|
||||
for domain in self.env.domains.sorted():
|
||||
for domain in domains.sorted():
|
||||
if domain.name == 'std':
|
||||
continue # we did this one already
|
||||
try:
|
||||
results.extend(
|
||||
domain.resolve_any_xref(
|
||||
self.env, refdoc, self.app.builder, target, node, contnode
|
||||
)
|
||||
results += domain.resolve_any_xref(
|
||||
env, ref_doc, builder, target, node, contnode
|
||||
)
|
||||
except NotImplementedError:
|
||||
# the domain doesn't yet support the new interface
|
||||
# we have to manually collect possible references (SLOW)
|
||||
for role in domain.roles:
|
||||
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):
|
||||
results.append((f'{domain.name}:{role}', res))
|
||||
@ -180,29 +216,23 @@ class ReferencesResolver(SphinxPostTransform):
|
||||
return f':{name}:`{reftitle}`'
|
||||
|
||||
candidates = ' or '.join(starmap(stringify, results))
|
||||
logger.warning(
|
||||
__(
|
||||
"more than one target found for 'any' cross-"
|
||||
'reference %r: could be %s'
|
||||
),
|
||||
target,
|
||||
candidates,
|
||||
location=node,
|
||||
type='ref',
|
||||
subtype='any',
|
||||
msg = __(
|
||||
"more than one target found for 'any' cross-reference %r: could be %s"
|
||||
)
|
||||
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
|
||||
# approximately correct.
|
||||
res_domain = res_role.split(':')[0]
|
||||
res_domain = res_role.partition(':')[0]
|
||||
if (
|
||||
len(newnode) > 0
|
||||
and isinstance(newnode[0], nodes.Element)
|
||||
and newnode[0].get('classes')
|
||||
len(new_node) > 0
|
||||
and isinstance(new_node[0], nodes.Element)
|
||||
and new_node[0].get('classes')
|
||||
):
|
||||
newnode[0]['classes'].append(res_domain)
|
||||
newnode[0]['classes'].append(res_role.replace(':', '-'))
|
||||
return newnode
|
||||
new_node[0]['classes'].extend((res_domain, res_role.replace(':', '-')))
|
||||
return new_node
|
||||
|
||||
def warn_missing_reference(
|
||||
self,
|
||||
|
Loading…
Reference in New Issue
Block a user