mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
intersphinx: Create an `_InventoryItem` type (#13248)
This commit is contained in:
@@ -37,9 +37,10 @@ def inspect_main(argv: list[str], /) -> int:
|
||||
for key in sorted(inv_data or {}):
|
||||
print(key)
|
||||
inv_entries = sorted(inv_data[key].items())
|
||||
for entry, (_proj, _ver, url_path, display_name) in inv_entries:
|
||||
for entry, inv_item in inv_entries:
|
||||
display_name = inv_item.display_name
|
||||
display_name = display_name * (display_name != '-')
|
||||
print(f' {entry:<40} {display_name:<40}: {url_path}')
|
||||
print(f' {entry:<40} {display_name:<40}: {inv_item.uri}')
|
||||
except ValueError as exc:
|
||||
print(exc.args[0] % exc.args[1:], file=sys.stderr)
|
||||
return 1
|
||||
|
||||
@@ -30,30 +30,33 @@ if TYPE_CHECKING:
|
||||
from sphinx.domains._domains_container import _DomainsContainer
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.ext.intersphinx._shared import InventoryName
|
||||
from sphinx.util.typing import Inventory, InventoryItem, RoleFunction
|
||||
from sphinx.util.inventory import _InventoryItem
|
||||
from sphinx.util.typing import Inventory, RoleFunction
|
||||
|
||||
|
||||
def _create_element_from_result(
|
||||
domain_name: str,
|
||||
inv_name: InventoryName | None,
|
||||
data: InventoryItem,
|
||||
inv_item: _InventoryItem,
|
||||
node: pending_xref,
|
||||
contnode: TextElement,
|
||||
) -> nodes.reference:
|
||||
proj, version, uri, dispname = data
|
||||
uri = inv_item.uri
|
||||
if '://' not in uri and node.get('refdoc'):
|
||||
# get correct path in case of subdirectories
|
||||
uri = (_relative_path(Path(), Path(node['refdoc']).parent) / uri).as_posix()
|
||||
if version:
|
||||
reftitle = _('(in %s v%s)') % (proj, version)
|
||||
if inv_item.project_version:
|
||||
reftitle = _('(in %s v%s)') % (inv_item.project_name, inv_item.project_version)
|
||||
else:
|
||||
reftitle = _('(in %s)') % (proj,)
|
||||
reftitle = _('(in %s)') % (inv_item.project_name,)
|
||||
|
||||
newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle)
|
||||
if node.get('refexplicit'):
|
||||
# use whatever title was given
|
||||
newnode.append(contnode)
|
||||
elif dispname == '-' or (domain_name == 'std' and node['reftype'] == 'keyword'):
|
||||
elif inv_item.display_name == '-' or (
|
||||
domain_name == 'std' and node['reftype'] == 'keyword'
|
||||
):
|
||||
# use whatever title was given, but strip prefix
|
||||
title = contnode.astext()
|
||||
if inv_name is not None and title.startswith(inv_name + ':'):
|
||||
@@ -66,7 +69,7 @@ def _create_element_from_result(
|
||||
newnode.append(contnode)
|
||||
else:
|
||||
# else use the given display name (used for :ref:)
|
||||
newnode.append(contnode.__class__(dispname, dispname))
|
||||
newnode.append(contnode.__class__(inv_item.display_name, inv_item.display_name))
|
||||
return newnode
|
||||
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@ from __future__ import annotations
|
||||
|
||||
import posixpath
|
||||
import re
|
||||
import warnings
|
||||
import zlib
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx10Warning
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
|
||||
@@ -15,12 +17,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import os
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Protocol
|
||||
from collections.abc import Callable, Iterator, Sequence
|
||||
from typing import NoReturn, Protocol
|
||||
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.util.typing import Inventory, InventoryItem
|
||||
from sphinx.util.typing import Inventory
|
||||
|
||||
# Readable file stream for inventory loading
|
||||
class _SupportsRead(Protocol):
|
||||
@@ -82,8 +84,12 @@ class InventoryFile:
|
||||
else:
|
||||
item_type = f'py:{item_type}'
|
||||
location += f'#{name}'
|
||||
inv_item: InventoryItem = projname, version, location, '-'
|
||||
invdata.setdefault(item_type, {})[name] = inv_item
|
||||
invdata.setdefault(item_type, {})[name] = _InventoryItem(
|
||||
project_name=projname,
|
||||
project_version=version,
|
||||
uri=location,
|
||||
display_name='-',
|
||||
)
|
||||
return invdata
|
||||
|
||||
@classmethod
|
||||
@@ -148,8 +154,12 @@ class InventoryFile:
|
||||
if location.endswith('$'):
|
||||
location = location[:-1] + name
|
||||
location = posixpath.join(uri, location)
|
||||
inv_item: InventoryItem = projname, version, location, dispname
|
||||
invdata.setdefault(type, {})[name] = inv_item
|
||||
invdata.setdefault(type, {})[name] = _InventoryItem(
|
||||
project_name=projname,
|
||||
project_version=version,
|
||||
uri=location,
|
||||
display_name=dispname,
|
||||
)
|
||||
for ambiguity in actual_ambiguities:
|
||||
logger.info(
|
||||
__('inventory <%s> contains multiple definitions for %s'),
|
||||
@@ -194,3 +204,89 @@ class InventoryFile:
|
||||
entry = f'{fullname} {domain.name}:{type} {prio} {uri} {dispname}\n'
|
||||
f.write(compressor.compress(entry.encode()))
|
||||
f.write(compressor.flush())
|
||||
|
||||
|
||||
class _InventoryItem:
|
||||
__slots__ = 'project_name', 'project_version', 'uri', 'display_name'
|
||||
|
||||
project_name: str
|
||||
project_version: str
|
||||
uri: str
|
||||
display_name: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
project_name: str,
|
||||
project_version: str,
|
||||
uri: str,
|
||||
display_name: str,
|
||||
) -> None:
|
||||
object.__setattr__(self, 'project_name', project_name)
|
||||
object.__setattr__(self, 'project_version', project_version)
|
||||
object.__setattr__(self, 'uri', uri)
|
||||
object.__setattr__(self, 'display_name', display_name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
'_InventoryItem('
|
||||
f'project_name={self.project_name!r}, '
|
||||
f'project_version={self.project_version!r}, '
|
||||
f'uri={self.uri!r}, '
|
||||
f'display_name={self.display_name!r}'
|
||||
')'
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, _InventoryItem):
|
||||
return NotImplemented
|
||||
return (
|
||||
self.project_name == other.project_name
|
||||
and self.project_version == other.project_version
|
||||
and self.uri == other.uri
|
||||
and self.display_name == other.display_name
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((
|
||||
self.project_name,
|
||||
self.project_version,
|
||||
self.uri,
|
||||
self.display_name,
|
||||
))
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> NoReturn:
|
||||
msg = '_InventoryItem is immutable'
|
||||
raise AttributeError(msg)
|
||||
|
||||
def __delattr__(self, key: str) -> NoReturn:
|
||||
msg = '_InventoryItem is immutable'
|
||||
raise AttributeError(msg)
|
||||
|
||||
def __getstate__(self) -> tuple[str, str, str, str]:
|
||||
return self.project_name, self.project_version, self.uri, self.display_name
|
||||
|
||||
def __setstate__(self, state: tuple[str, str, str, str]) -> None:
|
||||
project_name, project_version, uri, display_name = state
|
||||
object.__setattr__(self, 'project_name', project_name)
|
||||
object.__setattr__(self, 'project_version', project_version)
|
||||
object.__setattr__(self, 'uri', uri)
|
||||
object.__setattr__(self, 'display_name', display_name)
|
||||
|
||||
def __getitem__(self, key: int | slice) -> str | tuple[str, ...]:
|
||||
warnings.warn(
|
||||
'The tuple interface for _InventoryItem objects is deprecated.',
|
||||
RemovedInSphinx10Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
tpl = self.project_name, self.project_version, self.uri, self.display_name
|
||||
return tpl[key]
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
warnings.warn(
|
||||
'The iter() interface for _InventoryItem objects is deprecated.',
|
||||
RemovedInSphinx10Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
tpl = self.project_name, self.project_version, self.uri, self.display_name
|
||||
return iter(tpl)
|
||||
|
||||
@@ -23,6 +23,7 @@ if TYPE_CHECKING:
|
||||
from typing_extensions import TypeIs
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util.inventory import _InventoryItem
|
||||
|
||||
_RestifyMode: TypeAlias = Literal[
|
||||
'fully-qualified-except-typing',
|
||||
@@ -110,13 +111,7 @@ OptionSpec: TypeAlias = dict[str, Callable[[str], typing.Any]]
|
||||
TitleGetter: TypeAlias = Callable[[nodes.Node], str]
|
||||
|
||||
# inventory data on memory
|
||||
InventoryItem: TypeAlias = tuple[
|
||||
str, # project name
|
||||
str, # project version
|
||||
str, # URL
|
||||
str, # display name
|
||||
]
|
||||
Inventory: TypeAlias = dict[str, dict[str, InventoryItem]]
|
||||
Inventory: TypeAlias = dict[str, dict[str, '_InventoryItem']]
|
||||
|
||||
|
||||
class ExtensionMetadata(typing.TypedDict, total=False):
|
||||
|
||||
@@ -6,7 +6,7 @@ import posixpath
|
||||
|
||||
import pytest
|
||||
|
||||
from sphinx.util.inventory import InventoryFile
|
||||
from sphinx.util.inventory import InventoryFile, _InventoryItem
|
||||
|
||||
|
||||
@pytest.mark.sphinx('dirhtml', testroot='builder-dirhtml')
|
||||
@@ -30,28 +30,33 @@ def test_dirhtml(app):
|
||||
invdata = InventoryFile.load(f, 'path/to', posixpath.join)
|
||||
|
||||
assert 'index' in invdata.get('std:doc', {})
|
||||
assert invdata['std:doc']['index'] == ('Project name not set', '', 'path/to/', '-')
|
||||
assert invdata['std:doc']['index'] == _InventoryItem(
|
||||
project_name='Project name not set',
|
||||
project_version='',
|
||||
uri='path/to/',
|
||||
display_name='-',
|
||||
)
|
||||
|
||||
assert 'foo/index' in invdata.get('std:doc', {})
|
||||
assert invdata['std:doc']['foo/index'] == (
|
||||
'Project name not set',
|
||||
'',
|
||||
'path/to/foo/',
|
||||
'-',
|
||||
assert invdata['std:doc']['foo/index'] == _InventoryItem(
|
||||
project_name='Project name not set',
|
||||
project_version='',
|
||||
uri='path/to/foo/',
|
||||
display_name='-',
|
||||
)
|
||||
|
||||
assert 'index' in invdata.get('std:label', {})
|
||||
assert invdata['std:label']['index'] == (
|
||||
'Project name not set',
|
||||
'',
|
||||
'path/to/#index',
|
||||
'-',
|
||||
assert invdata['std:label']['index'] == _InventoryItem(
|
||||
project_name='Project name not set',
|
||||
project_version='',
|
||||
uri='path/to/#index',
|
||||
display_name='-',
|
||||
)
|
||||
|
||||
assert 'foo' in invdata.get('std:label', {})
|
||||
assert invdata['std:label']['foo'] == (
|
||||
'Project name not set',
|
||||
'',
|
||||
'path/to/foo/#foo',
|
||||
'foo/index',
|
||||
assert invdata['std:label']['foo'] == _InventoryItem(
|
||||
project_name='Project name not set',
|
||||
project_version='',
|
||||
uri='path/to/foo/#foo',
|
||||
display_name='foo/index',
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import pytest
|
||||
from sphinx._cli.util.errors import strip_escape_sequences
|
||||
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
|
||||
from sphinx.errors import ConfigError
|
||||
from sphinx.util.inventory import InventoryFile
|
||||
from sphinx.util.inventory import InventoryFile, _InventoryItem
|
||||
|
||||
from tests.test_builders.xpath_data import FIGURE_CAPTION
|
||||
from tests.test_builders.xpath_util import check_xpath
|
||||
@@ -233,36 +233,36 @@ def test_html_inventory(app):
|
||||
'genindex',
|
||||
'search',
|
||||
}
|
||||
assert invdata['std:label']['modindex'] == (
|
||||
'Project name not set',
|
||||
'',
|
||||
'https://www.google.com/py-modindex.html',
|
||||
'Module Index',
|
||||
assert invdata['std:label']['modindex'] == _InventoryItem(
|
||||
project_name='Project name not set',
|
||||
project_version='',
|
||||
uri='https://www.google.com/py-modindex.html',
|
||||
display_name='Module Index',
|
||||
)
|
||||
assert invdata['std:label']['py-modindex'] == (
|
||||
'Project name not set',
|
||||
'',
|
||||
'https://www.google.com/py-modindex.html',
|
||||
'Python Module Index',
|
||||
assert invdata['std:label']['py-modindex'] == _InventoryItem(
|
||||
project_name='Project name not set',
|
||||
project_version='',
|
||||
uri='https://www.google.com/py-modindex.html',
|
||||
display_name='Python Module Index',
|
||||
)
|
||||
assert invdata['std:label']['genindex'] == (
|
||||
'Project name not set',
|
||||
'',
|
||||
'https://www.google.com/genindex.html',
|
||||
'Index',
|
||||
assert invdata['std:label']['genindex'] == _InventoryItem(
|
||||
project_name='Project name not set',
|
||||
project_version='',
|
||||
uri='https://www.google.com/genindex.html',
|
||||
display_name='Index',
|
||||
)
|
||||
assert invdata['std:label']['search'] == (
|
||||
'Project name not set',
|
||||
'',
|
||||
'https://www.google.com/search.html',
|
||||
'Search Page',
|
||||
assert invdata['std:label']['search'] == _InventoryItem(
|
||||
project_name='Project name not set',
|
||||
project_version='',
|
||||
uri='https://www.google.com/search.html',
|
||||
display_name='Search Page',
|
||||
)
|
||||
assert set(invdata['std:doc'].keys()) == {'index'}
|
||||
assert invdata['std:doc']['index'] == (
|
||||
'Project name not set',
|
||||
'',
|
||||
'https://www.google.com/index.html',
|
||||
'The basic Sphinx documentation for testing',
|
||||
assert invdata['std:doc']['index'] == _InventoryItem(
|
||||
project_name='Project name not set',
|
||||
project_version='',
|
||||
uri='https://www.google.com/index.html',
|
||||
display_name='The basic Sphinx documentation for testing',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ from sphinx.ext.intersphinx._load import (
|
||||
)
|
||||
from sphinx.ext.intersphinx._resolve import missing_reference
|
||||
from sphinx.ext.intersphinx._shared import _IntersphinxProject
|
||||
from sphinx.util.inventory import _InventoryItem
|
||||
|
||||
from tests.test_util.intersphinx_data import (
|
||||
INVENTORY_V2,
|
||||
@@ -155,11 +156,11 @@ def test_missing_reference(tmp_path, app):
|
||||
load_mappings(app)
|
||||
inv = app.env.intersphinx_inventory
|
||||
|
||||
assert inv['py:module']['module2'] == (
|
||||
'foo',
|
||||
'2.0',
|
||||
'https://docs.python.org/foo.html#module-module2',
|
||||
'-',
|
||||
assert inv['py:module']['module2'] == _InventoryItem(
|
||||
project_name='foo',
|
||||
project_version='2.0',
|
||||
uri='https://docs.python.org/foo.html#module-module2',
|
||||
display_name='-',
|
||||
)
|
||||
|
||||
# check resolution when a target is found
|
||||
|
||||
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from sphinx.ext.intersphinx._shared import InventoryAdapter
|
||||
from sphinx.testing.util import SphinxTestApp
|
||||
from sphinx.util.inventory import _InventoryItem
|
||||
|
||||
from tests.utils import http_server
|
||||
|
||||
@@ -18,7 +19,6 @@ if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
from typing import BinaryIO
|
||||
|
||||
from sphinx.util.typing import InventoryItem
|
||||
|
||||
BASE_CONFIG = {
|
||||
'extensions': ['sphinx.ext.intersphinx'],
|
||||
@@ -109,10 +109,14 @@ class IntersphinxProject:
|
||||
"""The :confval:`intersphinx_mapping` record for this project."""
|
||||
return {self.name: (self.url, self.file)}
|
||||
|
||||
def normalise(self, entry: InventoryEntry) -> tuple[str, InventoryItem]:
|
||||
def normalise(self, entry: InventoryEntry) -> tuple[str, _InventoryItem]:
|
||||
"""Format an inventory entry as if it were part of this project."""
|
||||
url = posixpath.join(self.url, entry.uri)
|
||||
return entry.name, (self.safe_name, self.safe_version, url, entry.display_name)
|
||||
return entry.name, _InventoryItem(
|
||||
project_name=self.safe_name,
|
||||
project_version=self.safe_version,
|
||||
uri=posixpath.join(self.url, entry.uri),
|
||||
display_name=entry.display_name,
|
||||
)
|
||||
|
||||
|
||||
class FakeInventory:
|
||||
|
||||
@@ -8,7 +8,7 @@ import pytest
|
||||
|
||||
import sphinx.locale
|
||||
from sphinx.testing.util import SphinxTestApp
|
||||
from sphinx.util.inventory import InventoryFile
|
||||
from sphinx.util.inventory import InventoryFile, _InventoryItem
|
||||
|
||||
from tests.test_util.intersphinx_data import (
|
||||
INVENTORY_V1,
|
||||
@@ -23,17 +23,17 @@ if TYPE_CHECKING:
|
||||
|
||||
def test_read_inventory_v1():
|
||||
invdata = InventoryFile.loads(INVENTORY_V1, uri='/util')
|
||||
assert invdata['py:module']['module'] == (
|
||||
'foo',
|
||||
'1.0',
|
||||
'/util/foo.html#module-module',
|
||||
'-',
|
||||
assert invdata['py:module']['module'] == _InventoryItem(
|
||||
project_name='foo',
|
||||
project_version='1.0',
|
||||
uri='/util/foo.html#module-module',
|
||||
display_name='-',
|
||||
)
|
||||
assert invdata['py:class']['module.cls'] == (
|
||||
'foo',
|
||||
'1.0',
|
||||
'/util/foo.html#module.cls',
|
||||
'-',
|
||||
assert invdata['py:class']['module.cls'] == _InventoryItem(
|
||||
project_name='foo',
|
||||
project_version='1.0',
|
||||
uri='/util/foo.html#module.cls',
|
||||
display_name='-',
|
||||
)
|
||||
|
||||
|
||||
@@ -41,35 +41,35 @@ def test_read_inventory_v2():
|
||||
invdata = InventoryFile.loads(INVENTORY_V2, uri='/util')
|
||||
|
||||
assert len(invdata['py:module']) == 2
|
||||
assert invdata['py:module']['module1'] == (
|
||||
'foo',
|
||||
'2.0',
|
||||
'/util/foo.html#module-module1',
|
||||
'Long Module desc',
|
||||
assert invdata['py:module']['module1'] == _InventoryItem(
|
||||
project_name='foo',
|
||||
project_version='2.0',
|
||||
uri='/util/foo.html#module-module1',
|
||||
display_name='Long Module desc',
|
||||
)
|
||||
assert invdata['py:module']['module2'] == (
|
||||
'foo',
|
||||
'2.0',
|
||||
'/util/foo.html#module-module2',
|
||||
'-',
|
||||
assert invdata['py:module']['module2'] == _InventoryItem(
|
||||
project_name='foo',
|
||||
project_version='2.0',
|
||||
uri='/util/foo.html#module-module2',
|
||||
display_name='-',
|
||||
)
|
||||
assert invdata['py:function']['module1.func'][2] == (
|
||||
assert invdata['py:function']['module1.func'].uri == (
|
||||
'/util/sub/foo.html#module1.func'
|
||||
)
|
||||
assert invdata['c:function']['CFunc'][2] == '/util/cfunc.html#CFunc'
|
||||
assert invdata['std:term']['a term'][2] == '/util/glossary.html#term-a-term'
|
||||
assert invdata['std:term']['a term including:colon'][2] == (
|
||||
assert invdata['c:function']['CFunc'].uri == '/util/cfunc.html#CFunc'
|
||||
assert invdata['std:term']['a term'].uri == '/util/glossary.html#term-a-term'
|
||||
assert invdata['std:term']['a term including:colon'].uri == (
|
||||
'/util/glossary.html#term-a-term-including-colon'
|
||||
)
|
||||
|
||||
|
||||
def test_read_inventory_v2_not_having_version():
|
||||
invdata = InventoryFile.loads(INVENTORY_V2_NO_VERSION, uri='/util')
|
||||
assert invdata['py:module']['module1'] == (
|
||||
'foo',
|
||||
'',
|
||||
'/util/foo.html#module-module1',
|
||||
'Long Module desc',
|
||||
assert invdata['py:module']['module1'] == _InventoryItem(
|
||||
project_name='foo',
|
||||
project_version='',
|
||||
uri='/util/foo.html#module-module1',
|
||||
display_name='Long Module desc',
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user