Coverage for src/mkdocstrings/inventory.py: 31.94%
56 statements
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 18:59 +0200
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 18:59 +0200
1"""Module responsible for the objects inventory."""
3# Credits to Brian Skinn and the sphobjinv project:
4# https://github.com/bskinn/sphobjinv
6from __future__ import annotations
8import re
9import zlib
10from textwrap import dedent
11from typing import TYPE_CHECKING, BinaryIO
13if TYPE_CHECKING:
14 from collections.abc import Collection
17class InventoryItem:
18 """Inventory item."""
20 def __init__(
21 self,
22 name: str,
23 domain: str,
24 role: str,
25 uri: str,
26 priority: int = 1,
27 dispname: str | None = None,
28 ):
29 """Initialize the object.
31 Arguments:
32 name: The item name.
33 domain: The item domain, like 'python' or 'crystal'.
34 role: The item role, like 'class' or 'method'.
35 uri: The item URI.
36 priority: The item priority. Only used internally by mkdocstrings and Sphinx.
37 dispname: The item display name.
38 """
39 self.name: str = name
40 self.domain: str = domain
41 self.role: str = role
42 self.uri: str = uri
43 self.priority: int = priority
44 self.dispname: str = dispname or name
46 def format_sphinx(self) -> str:
47 """Format this item as a Sphinx inventory line.
49 Returns:
50 A line formatted for an `objects.inv` file.
51 """
52 dispname = self.dispname
53 if dispname == self.name:
54 dispname = "-"
55 uri = self.uri
56 if uri.endswith(self.name):
57 uri = uri[: -len(self.name)] + "$"
58 return f"{self.name} {self.domain}:{self.role} {self.priority} {uri} {dispname}"
60 sphinx_item_regex = re.compile(r"^(.+?)\s+(\S+):(\S+)\s+(-?\d+)\s+(\S+)\s*(.*)$")
62 @classmethod
63 def parse_sphinx(cls, line: str) -> InventoryItem:
64 """Parse a line from a Sphinx v2 inventory file and return an `InventoryItem` from it."""
65 match = cls.sphinx_item_regex.search(line)
66 if not match:
67 raise ValueError(line)
68 name, domain, role, priority, uri, dispname = match.groups()
69 if uri.endswith("$"):
70 uri = uri[:-1] + name
71 if dispname == "-":
72 dispname = name
73 return cls(name, domain, role, uri, int(priority), dispname)
76class Inventory(dict):
77 """Inventory of collected and rendered objects."""
79 def __init__(self, items: list[InventoryItem] | None = None, project: str = "project", version: str = "0.0.0"):
80 """Initialize the object.
82 Arguments:
83 items: A list of items.
84 project: The project name.
85 version: The project version.
86 """
87 super().__init__()
88 items = items or []
89 for item in items: 89 ↛ 90line 89 didn't jump to line 90 because the loop on line 89 never started
90 self[item.name] = item
91 self.project = project
92 self.version = version
94 def register(
95 self,
96 name: str,
97 domain: str,
98 role: str,
99 uri: str,
100 priority: int = 1,
101 dispname: str | None = None,
102 ) -> None:
103 """Create and register an item.
105 Arguments:
106 name: The item name.
107 domain: The item domain, like 'python' or 'crystal'.
108 role: The item role, like 'class' or 'method'.
109 uri: The item URI.
110 priority: The item priority. Only used internally by mkdocstrings and Sphinx.
111 dispname: The item display name.
112 """
113 self[name] = InventoryItem(
114 name=name,
115 domain=domain,
116 role=role,
117 uri=uri,
118 priority=priority,
119 dispname=dispname,
120 )
122 def format_sphinx(self) -> bytes:
123 """Format this inventory as a Sphinx `objects.inv` file.
125 Returns:
126 The inventory as bytes.
127 """
128 header = (
129 dedent(
130 f"""
131 # Sphinx inventory version 2
132 # Project: {self.project}
133 # Version: {self.version}
134 # The remainder of this file is compressed using zlib.
135 """,
136 )
137 .lstrip()
138 .encode("utf8")
139 )
141 lines = [
142 item.format_sphinx().encode("utf8")
143 for item in sorted(self.values(), key=lambda item: (item.domain, item.name))
144 ]
145 return header + zlib.compress(b"\n".join(lines) + b"\n", 9)
147 @classmethod
148 def parse_sphinx(cls, in_file: BinaryIO, *, domain_filter: Collection[str] = ()) -> Inventory:
149 """Parse a Sphinx v2 inventory file and return an `Inventory` from it.
151 Arguments:
152 in_file: The binary file-like object to read from.
153 domain_filter: A collection of domain values to allow (and filter out all other ones).
155 Returns:
156 An inventory containing the collected items.
157 """
158 for _ in range(4):
159 in_file.readline()
160 lines = zlib.decompress(in_file.read()).splitlines()
161 items = [InventoryItem.parse_sphinx(line.decode("utf8")) for line in lines]
162 if domain_filter:
163 items = [item for item in items if item.domain in domain_filter]
164 return cls(items)