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

1"""Module responsible for the objects inventory.""" 

2 

3# Credits to Brian Skinn and the sphobjinv project: 

4# https://github.com/bskinn/sphobjinv 

5 

6from __future__ import annotations 

7 

8import re 

9import zlib 

10from textwrap import dedent 

11from typing import TYPE_CHECKING, BinaryIO 

12 

13if TYPE_CHECKING: 

14 from collections.abc import Collection 

15 

16 

17class InventoryItem: 

18 """Inventory item.""" 

19 

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. 

30 

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 

45 

46 def format_sphinx(self) -> str: 

47 """Format this item as a Sphinx inventory line. 

48 

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}" 

59 

60 sphinx_item_regex = re.compile(r"^(.+?)\s+(\S+):(\S+)\s+(-?\d+)\s+(\S+)\s*(.*)$") 

61 

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) 

74 

75 

76class Inventory(dict): 

77 """Inventory of collected and rendered objects.""" 

78 

79 def __init__(self, items: list[InventoryItem] | None = None, project: str = "project", version: str = "0.0.0"): 

80 """Initialize the object. 

81 

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 

93 

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. 

104 

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 ) 

121 

122 def format_sphinx(self) -> bytes: 

123 """Format this inventory as a Sphinx `objects.inv` file. 

124 

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 ) 

140 

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) 

146 

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. 

150 

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). 

154 

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)