2424"""
2525
2626import concurrent .futures
27+ import copy
2728import functools
2829import posixpath
2930import sys
3031import time
3132from os import path
32- from typing import IO , Any , Dict , List , Tuple
33+ from typing import IO , Any , Dict , List , Optional , Tuple
3334from urllib .parse import urlsplit , urlunsplit
3435
35- from docutils import nodes
36- from docutils .nodes import TextElement
37- from docutils .utils import relative_path
36+ from docutils .nodes import Element , TextElement
3837
3938import sphinx
4039from sphinx .addnodes import pending_xref
4140from sphinx .application import Sphinx
4241from sphinx .builders .html import INVENTORY_FILENAME
4342from sphinx .config import Config
4443from sphinx .environment import BuildEnvironment
45- from sphinx .locale import _ , __
44+ from sphinx .locale import __
4645from sphinx .util import logging , requests
47- from sphinx .util .inventory import InventoryFile
48- from sphinx .util .nodes import find_pending_xref_condition
46+ from sphinx .util .inventory import InventoryFile , InventoryItemSet
4947from sphinx .util .typing import Inventory
5048
5149logger = logging .getLogger (__name__ )
@@ -60,7 +58,13 @@ def __init__(self, env: BuildEnvironment) -> None:
6058 if not hasattr (env , 'intersphinx_cache' ):
6159 self .env .intersphinx_cache = {} # type: ignore
6260 self .env .intersphinx_inventory = {} # type: ignore
63- self .env .intersphinx_named_inventory = {} # type: ignore
61+ self .env .intersphinx_by_domain_inventory = {} # type: ignore
62+ self ._clear_by_domain_inventory ()
63+
64+ def _clear_by_domain_inventory (self ) -> None :
65+ for domain in self .env .domains .values ():
66+ inv = copy .deepcopy (domain .initial_intersphinx_inventory )
67+ self .env .intersphinx_by_domain_inventory [domain .name ] = inv # type: ignore
6468
6569 @property
6670 def cache (self ) -> Dict [str , Tuple [str , int , Inventory ]]:
@@ -71,12 +75,13 @@ def main_inventory(self) -> Inventory:
7175 return self .env .intersphinx_inventory # type: ignore
7276
7377 @property
74- def named_inventory (self ) -> Dict [str , Inventory ]:
75- return self .env .intersphinx_named_inventory # type: ignore
78+ def by_domain_inventory (self ) -> Dict [str , Any ]:
79+ return self .env .intersphinx_by_domain_inventory # type: ignore
7680
7781 def clear (self ) -> None :
7882 self .env .intersphinx_inventory .clear () # type: ignore
79- self .env .intersphinx_named_inventory .clear () # type: ignore
83+ self .env .intersphinx_by_domain_inventory .clear () # type: ignore
84+ self ._clear_by_domain_inventory ()
8085
8186
8287def _strip_basic_auth (url : str ) -> str :
@@ -241,114 +246,60 @@ def load_mappings(app: Sphinx) -> None:
241246
242247 if any (updated ):
243248 inventories .clear ()
244-
245- # Duplicate values in different inventories will shadow each
246- # other; which one will override which can vary between builds
247- # since they are specified using an unordered dict. To make
248- # it more consistent, we sort the named inventories and then
249- # add the unnamed inventories last. This means that the
250- # unnamed inventories will shadow the named ones but the named
251- # ones can still be accessed when the name is specified.
249+ # old stuff, still used in the tests
252250 cached_vals = list (inventories .cache .values ())
253251 named_vals = sorted (v for v in cached_vals if v [0 ])
254252 unnamed_vals = [v for v in cached_vals if not v [0 ]]
255253 for name , _x , invdata in named_vals + unnamed_vals :
256- if name :
257- inventories .named_inventory [name ] = invdata
258254 for type , objects in invdata .items ():
259255 inventories .main_inventory .setdefault (type , {}).update (objects )
256+ # end of old stuff
257+
258+ # first collect all entries indexed by domain, object name, and object type
259+ # domain -> (object_name, object_type) -> InventoryItemSet([(inv_name, inner_data)])
260+ entries = {} # type: Dict[str, Dict[Tuple[str, str], InventoryItemSet]]
261+ for inv_name , _x , inv_data in inventories .cache .values ():
262+ # inv_data: Inventory = Dict[str, Dict[str, InventoryInner]]
263+ for inv_type , inv_objects in inv_data .items ():
264+ # inv_objects: Dict[str, InventoryInner]
265+ assert ':' in inv_type
266+ domain_name , object_type = inv_type .split (':' )
267+ if domain_name not in app .env .domains :
268+ continue
269+ domain_entries = entries .setdefault (domain_name , {})
270+ for object_name , inner_data in inv_objects .items ():
271+ itemSet = domain_entries .setdefault (
272+ (object_name , object_type ), InventoryItemSet ())
273+ itemSet .append ((inv_name , inner_data ))
274+ # and then give the data to each domain
275+ for domain_name , domain_entries in entries .items ():
276+ domain = app .env .domains [domain_name ]
277+ domain_store = inventories .by_domain_inventory [domain_name ]
278+ domain .intersphinx_add_entries_v2 (domain_store , domain_entries )
260279
261280
262281def missing_reference (app : Sphinx , env : BuildEnvironment , node : pending_xref ,
263- contnode : TextElement ) -> nodes . reference :
282+ contnode : TextElement ) -> Optional [ Element ] :
264283 """Attempt to resolve a missing reference via intersphinx references."""
284+ typ = node ['reftype' ]
265285 target = node ['reftarget' ]
266286 inventories = InventoryAdapter (env )
267- objtypes : List [str ] = None
268- if node ['reftype' ] == 'any' :
269- # we search anything!
270- objtypes = ['%s:%s' % (domain .name , objtype )
271- for domain in env .domains .values ()
272- for objtype in domain .object_types ]
273- domain = None
287+ if typ == 'any' :
288+ for domain in env .domains .values ():
289+ domain_store = inventories .by_domain_inventory [domain .name ]
290+ res = domain .intersphinx_resolve_any_xref (
291+ env , domain_store , target , node , contnode )
292+ if res is not None :
293+ return res
294+ return None
274295 else :
275- domain = node .get ('refdomain' )
276- if not domain :
296+ domain_name = node .get ('refdomain' )
297+ if not domain_name :
277298 # only objects in domains are in the inventory
278299 return None
279- objtypes = env .get_domain (domain ).objtypes_for_role (node ['reftype' ])
280- if not objtypes :
281- return None
282- objtypes = ['%s:%s' % (domain , objtype ) for objtype in objtypes ]
283- if 'std:cmdoption' in objtypes :
284- # until Sphinx-1.6, cmdoptions are stored as std:option
285- objtypes .append ('std:option' )
286- if 'py:attribute' in objtypes :
287- # Since Sphinx-2.1, properties are stored as py:method
288- objtypes .append ('py:method' )
289-
290- # determine the contnode by pending_xref_condition
291- content = find_pending_xref_condition (node , 'resolved' )
292- if content :
293- # resolved condition found.
294- contnodes = content .children
295- contnode = content .children [0 ] # type: ignore
296- else :
297- # not resolved. Use the given contnode
298- contnodes = [contnode ]
299-
300- to_try = [(inventories .main_inventory , target )]
301- if domain :
302- full_qualified_name = env .get_domain (domain ).get_full_qualified_name (node )
303- if full_qualified_name :
304- to_try .append ((inventories .main_inventory , full_qualified_name ))
305- in_set = None
306- if ':' in target :
307- # first part may be the foreign doc set name
308- setname , newtarget = target .split (':' , 1 )
309- if setname in inventories .named_inventory :
310- in_set = setname
311- to_try .append ((inventories .named_inventory [setname ], newtarget ))
312- if domain :
313- node ['reftarget' ] = newtarget
314- full_qualified_name = env .get_domain (domain ).get_full_qualified_name (node )
315- if full_qualified_name :
316- to_try .append ((inventories .named_inventory [setname ], full_qualified_name ))
317- for inventory , target in to_try :
318- for objtype in objtypes :
319- if objtype not in inventory or target not in inventory [objtype ]:
320- continue
321- proj , version , uri , dispname = inventory [objtype ][target ]
322- if '://' not in uri and node .get ('refdoc' ):
323- # get correct path in case of subdirectories
324- uri = path .join (relative_path (node ['refdoc' ], '.' ), uri )
325- if version :
326- reftitle = _ ('(in %s v%s)' ) % (proj , version )
327- else :
328- reftitle = _ ('(in %s)' ) % (proj ,)
329- newnode = nodes .reference ('' , '' , internal = False , refuri = uri , reftitle = reftitle )
330- if node .get ('refexplicit' ):
331- # use whatever title was given
332- newnode .extend (contnodes )
333- elif dispname == '-' or \
334- (domain == 'std' and node ['reftype' ] == 'keyword' ):
335- # use whatever title was given, but strip prefix
336- title = contnode .astext ()
337- if in_set and title .startswith (in_set + ':' ):
338- newnode .append (contnode .__class__ (title [len (in_set ) + 1 :],
339- title [len (in_set ) + 1 :]))
340- else :
341- newnode .extend (contnodes )
342- else :
343- # else use the given display name (used for :ref:)
344- newnode .append (contnode .__class__ (dispname , dispname ))
345- return newnode
346- # at least get rid of the ':' in the target if no explicit title given
347- if in_set is not None and not node .get ('refexplicit' , True ):
348- if len (contnode ) and isinstance (contnode [0 ], nodes .Text ):
349- contnode [0 ] = nodes .Text (newtarget , contnode [0 ].rawsource )
350-
351- return None
300+ domain_store = inventories .by_domain_inventory [domain_name ]
301+ return env .domains [domain_name ].intersphinx_resolve_xref (
302+ env , domain_store , typ , target , node , contnode )
352303
353304
354305def normalize_intersphinx_mapping (app : Sphinx , config : Config ) -> None :
0 commit comments