1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
|
"""
sphinx.domains
~~~~~~~~~~~~~~
Support for domains, which are groupings of description directives
and roles describing e.g. constructs of one programming language.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import copy
from abc import ABC, abstractmethod
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, NamedTuple, Tuple,
Type, Union, cast)
from docutils import nodes
from docutils.nodes import Element, Node, system_message
from docutils.parsers.rst.states import Inliner
from sphinx.addnodes import pending_xref
from sphinx.errors import SphinxError
from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util.typing import RoleFunction
if TYPE_CHECKING:
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
class ObjType:
"""
An ObjType is the description for a type of object that a domain can
document. In the object_types attribute of Domain subclasses, object type
names are mapped to instances of this class.
Constructor arguments:
- *lname*: localized name of the type (do not include domain name)
- *roles*: all the roles that can refer to an object of this type
- *attrs*: object attributes -- currently only "searchprio" is known,
which defines the object's priority in the full-text search index,
see :meth:`Domain.get_objects()`.
"""
known_attrs = {
'searchprio': 1,
}
def __init__(self, lname: str, *roles: Any, **attrs: Any) -> None:
self.lname = lname
self.roles = roles # type: Tuple
self.attrs = self.known_attrs.copy() # type: Dict
self.attrs.update(attrs)
class IndexEntry(NamedTuple):
name: str
subtype: int
docname: str
anchor: str
extra: str
qualifier: str
descr: str
class Index(ABC):
"""
An Index is the description for a domain-specific index. To add an index to
a domain, subclass Index, overriding the three name attributes:
* `name` is an identifier used for generating file names.
It is also used for a hyperlink target for the index. Therefore, users can
refer the index page using ``ref`` role and a string which is combined
domain name and ``name`` attribute (ex. ``:ref:`py-modindex```).
* `localname` is the section title for the index.
* `shortname` is a short name for the index, for use in the relation bar in
HTML output. Can be empty to disable entries in the relation bar.
and providing a :meth:`generate()` method. Then, add the index class to
your domain's `indices` list. Extensions can add indices to existing
domains using :meth:`~sphinx.application.Sphinx.add_index_to_domain()`.
.. versionchanged:: 3.0
Index pages can be referred by domain name and index name via
:rst:role:`ref` role.
"""
name = None # type: str
localname = None # type: str
shortname = None # type: str
def __init__(self, domain: "Domain") -> None:
if self.name is None or self.localname is None:
raise SphinxError('Index subclass %s has no valid name or localname'
% self.__class__.__name__)
self.domain = domain
@abstractmethod
def generate(self, docnames: Iterable[str] = None
) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
"""Get entries for the index.
If ``docnames`` is given, restrict to entries referring to these
docnames.
The return value is a tuple of ``(content, collapse)``:
``collapse``
A boolean that determines if sub-entries should start collapsed (for
output formats that support collapsing sub-entries).
``content``:
A sequence of ``(letter, entries)`` tuples, where ``letter`` is the
"heading" for the given ``entries``, usually the starting letter, and
``entries`` is a sequence of single entries. Each entry is a sequence
``[name, subtype, docname, anchor, extra, qualifier, descr]``. The
items in this sequence have the following meaning:
``name``
The name of the index entry to be displayed.
``subtype``
The sub-entry related type. One of:
``0``
A normal entry.
``1``
An entry with sub-entries.
``2``
A sub-entry.
``docname``
*docname* where the entry is located.
``anchor``
Anchor for the entry within ``docname``
``extra``
Extra info for the entry.
``qualifier``
Qualifier for the description.
``descr``
Description for the entry.
Qualifier and description are not rendered for some output formats such
as LaTeX.
"""
raise NotImplementedError
class Domain:
"""
A Domain is meant to be a group of "object" description directives for
objects of a similar nature, and corresponding roles to create references to
them. Examples would be Python modules, classes, functions etc., elements
of a templating language, Sphinx roles and directives, etc.
Each domain has a separate storage for information about existing objects
and how to reference them in `self.data`, which must be a dictionary. It
also must implement several functions that expose the object information in
a uniform way to parts of Sphinx that allow the user to reference or search
for objects in a domain-agnostic way.
About `self.data`: since all object and cross-referencing information is
stored on a BuildEnvironment instance, the `domain.data` object is also
stored in the `env.domaindata` dict under the key `domain.name`. Before the
build process starts, every active domain is instantiated and given the
environment object; the `domaindata` dict must then either be nonexistent or
a dictionary whose 'version' key is equal to the domain class'
:attr:`data_version` attribute. Otherwise, `OSError` is raised and the
pickled environment is discarded.
"""
#: domain name: should be short, but unique
name = ''
#: domain label: longer, more descriptive (used in messages)
label = ''
#: type (usually directive) name -> ObjType instance
object_types = {} # type: Dict[str, ObjType]
#: directive name -> directive class
directives = {} # type: Dict[str, Any]
#: role name -> role callable
roles = {} # type: Dict[str, Union[RoleFunction, XRefRole]]
#: a list of Index subclasses
indices = [] # type: List[Type[Index]]
#: role name -> a warning message if reference is missing
dangling_warnings = {} # type: Dict[str, str]
#: node_class -> (enum_node_type, title_getter)
enumerable_nodes = {} # type: Dict[Type[Node], Tuple[str, Callable]]
#: data value for a fresh environment
initial_data = {} # type: Dict
#: data value
data = None # type: Dict
#: data version, bump this when the format of `self.data` changes
data_version = 0
def __init__(self, env: "BuildEnvironment") -> None:
self.env = env # type: BuildEnvironment
self._role_cache = {} # type: Dict[str, Callable]
self._directive_cache = {} # type: Dict[str, Callable]
self._role2type = {} # type: Dict[str, List[str]]
self._type2role = {} # type: Dict[str, str]
# convert class variables to instance one (to enhance through API)
self.object_types = dict(self.object_types)
self.directives = dict(self.directives)
self.roles = dict(self.roles)
self.indices = list(self.indices)
if self.name not in env.domaindata:
assert isinstance(self.initial_data, dict)
new_data = copy.deepcopy(self.initial_data)
new_data['version'] = self.data_version
self.data = env.domaindata[self.name] = new_data
else:
self.data = env.domaindata[self.name]
if self.data['version'] != self.data_version:
raise OSError('data of %r domain out of date' % self.label)
for name, obj in self.object_types.items():
for rolename in obj.roles:
self._role2type.setdefault(rolename, []).append(name)
self._type2role[name] = obj.roles[0] if obj.roles else ''
self.objtypes_for_role = self._role2type.get # type: Callable[[str], List[str]]
self.role_for_objtype = self._type2role.get # type: Callable[[str], str]
def setup(self) -> None:
"""Set up domain object."""
from sphinx.domains.std import StandardDomain
# Add special hyperlink target for index pages (ex. py-modindex)
std = cast(StandardDomain, self.env.get_domain('std'))
for index in self.indices:
if index.name and index.localname:
docname = "%s-%s" % (self.name, index.name)
std.note_hyperlink_target(docname, docname, '', index.localname)
def add_object_type(self, name: str, objtype: ObjType) -> None:
"""Add an object type."""
self.object_types[name] = objtype
if objtype.roles:
self._type2role[name] = objtype.roles[0]
else:
self._type2role[name] = ''
for role in objtype.roles:
self._role2type.setdefault(role, []).append(name)
def role(self, name: str) -> RoleFunction:
"""Return a role adapter function that always gives the registered
role its full name ('domain:name') as the first argument.
"""
if name in self._role_cache:
return self._role_cache[name]
if name not in self.roles:
return None
fullname = '%s:%s' % (self.name, name)
def role_adapter(typ: str, rawtext: str, text: str, lineno: int,
inliner: Inliner, options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
return self.roles[name](fullname, rawtext, text, lineno,
inliner, options, content)
self._role_cache[name] = role_adapter
return role_adapter
def directive(self, name: str) -> Callable:
"""Return a directive adapter class that always gives the registered
directive its full name ('domain:name') as ``self.name``.
"""
if name in self._directive_cache:
return self._directive_cache[name]
if name not in self.directives:
return None
fullname = '%s:%s' % (self.name, name)
BaseDirective = self.directives[name]
class DirectiveAdapter(BaseDirective): # type: ignore
def run(self) -> List[Node]:
self.name = fullname
return super().run()
self._directive_cache[name] = DirectiveAdapter
return DirectiveAdapter
# methods that should be overwritten
def clear_doc(self, docname: str) -> None:
"""Remove traces of a document in the domain-specific inventories."""
pass
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
"""Merge in data regarding *docnames* from a different domaindata
inventory (coming from a subprocess in parallel builds).
"""
raise NotImplementedError('merge_domaindata must be implemented in %s '
'to be able to do parallel builds!' %
self.__class__)
def process_doc(self, env: "BuildEnvironment", docname: str,
document: nodes.document) -> None:
"""Process a document after it is read by the environment."""
pass
def check_consistency(self) -> None:
"""Do consistency checks (**experimental**)."""
pass
def process_field_xref(self, pnode: pending_xref) -> None:
"""Process a pending xref created in a doc field.
For example, attach information about the current scope.
"""
pass
def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element
) -> Element:
"""Resolve the pending_xref *node* with the given *typ* and *target*.
This method should return a new node, to replace the xref node,
containing the *contnode* which is the markup content of the
cross-reference.
If no resolution can be found, None can be returned; the xref node will
then given to the :event:`missing-reference` event, and if that yields no
resolution, replaced by *contnode*.
The method can also raise :exc:`sphinx.environment.NoUri` to suppress
the :event:`missing-reference` event being emitted.
"""
pass
def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
target: str, node: pending_xref, contnode: Element
) -> List[Tuple[str, Element]]:
"""Resolve the pending_xref *node* with the given *target*.
The reference comes from an "any" or similar role, which means that we
don't know the type. Otherwise, the arguments are the same as for
:meth:`resolve_xref`.
The method must return a list (potentially empty) of tuples
``('domain:role', newnode)``, where ``'domain:role'`` is the name of a
role that could have created the same reference, e.g. ``'py:func'``.
``newnode`` is what :meth:`resolve_xref` would return.
.. versionadded:: 1.3
"""
raise NotImplementedError
def get_objects(self) -> Iterable[Tuple[str, str, str, str, str, int]]:
"""Return an iterable of "object descriptions".
Object descriptions are tuples with six items:
``name``
Fully qualified name.
``dispname``
Name to display when searching/linking.
``type``
Object type, a key in ``self.object_types``.
``docname``
The document where it is to be found.
``anchor``
The anchor name for the object.
``priority``
How "important" the object is (determines placement in search
results). One of:
``1``
Default priority (placed before full-text matches).
``0``
Object is important (placed before default-priority objects).
``2``
Object is unimportant (placed after full-text matches).
``-1``
Object should not show up in search at all.
"""
return []
def get_type_name(self, type: ObjType, primary: bool = False) -> str:
"""Return full name for given ObjType."""
if primary:
return type.lname
return _('%s %s') % (self.label, type.lname)
def get_enumerable_node_type(self, node: Node) -> str:
"""Get type of enumerable nodes (experimental)."""
enum_node_type, _ = self.enumerable_nodes.get(node.__class__, (None, None))
return enum_node_type
def get_full_qualified_name(self, node: Element) -> str:
"""Return full qualified name for given node."""
return None
|