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