Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Doc/tools/extensions/pyspecific.py
12 views
1
# -*- coding: utf-8 -*-
2
"""
3
pyspecific.py
4
~~~~~~~~~~~~~
5
6
Sphinx extension with Python doc-specific markup.
7
8
:copyright: 2008-2014 by Georg Brandl.
9
:license: Python license.
10
"""
11
12
import re
13
import io
14
from os import getenv, path
15
from time import asctime
16
from pprint import pformat
17
18
from docutils import nodes, utils
19
from docutils.io import StringOutput
20
from docutils.parsers.rst import Directive
21
from docutils.utils import new_document
22
from sphinx import addnodes
23
from sphinx.builders import Builder
24
from sphinx.domains.python import PyFunction, PyMethod
25
from sphinx.errors import NoUri
26
from sphinx.locale import _ as sphinx_gettext
27
from sphinx.util import logging
28
from sphinx.util.docutils import SphinxDirective
29
from sphinx.util.nodes import split_explicit_title
30
from sphinx.writers.text import TextWriter, TextTranslator
31
32
try:
33
# Sphinx 6+
34
from sphinx.util.display import status_iterator
35
except ImportError:
36
# Deprecated in Sphinx 6.1, will be removed in Sphinx 8
37
from sphinx.util import status_iterator
38
39
40
ISSUE_URI = 'https://bugs.python.org/issue?@action=redirect&bpo=%s'
41
GH_ISSUE_URI = 'https://github.com/python/cpython/issues/%s'
42
SOURCE_URI = 'https://github.com/python/cpython/tree/3.12/%s'
43
44
# monkey-patch reST parser to disable alphabetic and roman enumerated lists
45
from docutils.parsers.rst.states import Body
46
Body.enum.converters['loweralpha'] = \
47
Body.enum.converters['upperalpha'] = \
48
Body.enum.converters['lowerroman'] = \
49
Body.enum.converters['upperroman'] = lambda x: None
50
51
52
# Support for marking up and linking to bugs.python.org issues
53
54
def issue_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
55
issue = utils.unescape(text)
56
# sanity check: there are no bpo issues within these two values
57
if 47261 < int(issue) < 400000:
58
msg = inliner.reporter.error(f'The BPO ID {text!r} seems too high -- '
59
'use :gh:`...` for GitHub IDs', line=lineno)
60
prb = inliner.problematic(rawtext, rawtext, msg)
61
return [prb], [msg]
62
text = 'bpo-' + issue
63
refnode = nodes.reference(text, text, refuri=ISSUE_URI % issue)
64
return [refnode], []
65
66
67
# Support for marking up and linking to GitHub issues
68
69
def gh_issue_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
70
issue = utils.unescape(text)
71
# sanity check: all GitHub issues have ID >= 32426
72
# even though some of them are also valid BPO IDs
73
if int(issue) < 32426:
74
msg = inliner.reporter.error(f'The GitHub ID {text!r} seems too low -- '
75
'use :issue:`...` for BPO IDs', line=lineno)
76
prb = inliner.problematic(rawtext, rawtext, msg)
77
return [prb], [msg]
78
text = 'gh-' + issue
79
refnode = nodes.reference(text, text, refuri=GH_ISSUE_URI % issue)
80
return [refnode], []
81
82
83
# Support for linking to Python source files easily
84
85
def source_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
86
has_t, title, target = split_explicit_title(text)
87
title = utils.unescape(title)
88
target = utils.unescape(target)
89
refnode = nodes.reference(title, title, refuri=SOURCE_URI % target)
90
return [refnode], []
91
92
93
# Support for marking up implementation details
94
95
class ImplementationDetail(Directive):
96
97
has_content = True
98
final_argument_whitespace = True
99
100
# This text is copied to templates/dummy.html
101
label_text = 'CPython implementation detail:'
102
103
def run(self):
104
self.assert_has_content()
105
pnode = nodes.compound(classes=['impl-detail'])
106
label = sphinx_gettext(self.label_text)
107
content = self.content
108
add_text = nodes.strong(label, label)
109
self.state.nested_parse(content, self.content_offset, pnode)
110
content = nodes.inline(pnode[0].rawsource, translatable=True)
111
content.source = pnode[0].source
112
content.line = pnode[0].line
113
content += pnode[0].children
114
pnode[0].replace_self(nodes.paragraph(
115
'', '', add_text, nodes.Text(' '), content, translatable=False))
116
return [pnode]
117
118
119
# Support for documenting platform availability
120
121
class Availability(SphinxDirective):
122
123
has_content = True
124
required_arguments = 1
125
optional_arguments = 0
126
final_argument_whitespace = True
127
128
# known platform, libc, and threading implementations
129
known_platforms = frozenset({
130
"AIX", "Android", "BSD", "DragonFlyBSD", "Emscripten", "FreeBSD",
131
"Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris", "Unix", "VxWorks",
132
"WASI", "Windows", "macOS",
133
# libc
134
"BSD libc", "glibc", "musl",
135
# POSIX platforms with pthreads
136
"pthreads",
137
})
138
139
def run(self):
140
availability_ref = ':ref:`Availability <availability>`: '
141
avail_nodes, avail_msgs = self.state.inline_text(
142
availability_ref + self.arguments[0],
143
self.lineno)
144
pnode = nodes.paragraph(availability_ref + self.arguments[0],
145
'', *avail_nodes, *avail_msgs)
146
self.set_source_info(pnode)
147
cnode = nodes.container("", pnode, classes=["availability"])
148
self.set_source_info(cnode)
149
if self.content:
150
self.state.nested_parse(self.content, self.content_offset, cnode)
151
self.parse_platforms()
152
153
return [cnode]
154
155
def parse_platforms(self):
156
"""Parse platform information from arguments
157
158
Arguments is a comma-separated string of platforms. A platform may
159
be prefixed with "not " to indicate that a feature is not available.
160
161
Example::
162
163
.. availability:: Windows, Linux >= 4.2, not Emscripten, not WASI
164
165
Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not
166
parsed into separate tokens.
167
"""
168
platforms = {}
169
for arg in self.arguments[0].rstrip(".").split(","):
170
arg = arg.strip()
171
platform, _, version = arg.partition(" >= ")
172
if platform.startswith("not "):
173
version = False
174
platform = platform[4:]
175
elif not version:
176
version = True
177
platforms[platform] = version
178
179
unknown = set(platforms).difference(self.known_platforms)
180
if unknown:
181
cls = type(self)
182
logger = logging.getLogger(cls.__qualname__)
183
logger.warn(
184
f"Unknown platform(s) or syntax '{' '.join(sorted(unknown))}' "
185
f"in '.. availability:: {self.arguments[0]}', see "
186
f"{__file__}:{cls.__qualname__}.known_platforms for a set "
187
"known platforms."
188
)
189
190
return platforms
191
192
193
194
# Support for documenting audit event
195
196
def audit_events_purge(app, env, docname):
197
"""This is to remove from env.all_audit_events old traces of removed
198
documents.
199
"""
200
if not hasattr(env, 'all_audit_events'):
201
return
202
fresh_all_audit_events = {}
203
for name, event in env.all_audit_events.items():
204
event["source"] = [(d, t) for d, t in event["source"] if d != docname]
205
if event["source"]:
206
# Only keep audit_events that have at least one source.
207
fresh_all_audit_events[name] = event
208
env.all_audit_events = fresh_all_audit_events
209
210
211
def audit_events_merge(app, env, docnames, other):
212
"""In Sphinx parallel builds, this merges env.all_audit_events from
213
subprocesses.
214
215
all_audit_events is a dict of names, with values like:
216
{'source': [(docname, target), ...], 'args': args}
217
"""
218
if not hasattr(other, 'all_audit_events'):
219
return
220
if not hasattr(env, 'all_audit_events'):
221
env.all_audit_events = {}
222
for name, value in other.all_audit_events.items():
223
if name in env.all_audit_events:
224
env.all_audit_events[name]["source"].extend(value["source"])
225
else:
226
env.all_audit_events[name] = value
227
228
229
class AuditEvent(Directive):
230
231
has_content = True
232
required_arguments = 1
233
optional_arguments = 2
234
final_argument_whitespace = True
235
236
_label = [
237
"Raises an :ref:`auditing event <auditing>` {name} with no arguments.",
238
"Raises an :ref:`auditing event <auditing>` {name} with argument {args}.",
239
"Raises an :ref:`auditing event <auditing>` {name} with arguments {args}.",
240
]
241
242
@property
243
def logger(self):
244
cls = type(self)
245
return logging.getLogger(cls.__module__ + "." + cls.__name__)
246
247
def run(self):
248
name = self.arguments[0]
249
if len(self.arguments) >= 2 and self.arguments[1]:
250
args = (a.strip() for a in self.arguments[1].strip("'\"").split(","))
251
args = [a for a in args if a]
252
else:
253
args = []
254
255
label = sphinx_gettext(self._label[min(2, len(args))])
256
text = label.format(name="``{}``".format(name),
257
args=", ".join("``{}``".format(a) for a in args if a))
258
259
env = self.state.document.settings.env
260
if not hasattr(env, 'all_audit_events'):
261
env.all_audit_events = {}
262
263
new_info = {
264
'source': [],
265
'args': args
266
}
267
info = env.all_audit_events.setdefault(name, new_info)
268
if info is not new_info:
269
if not self._do_args_match(info['args'], new_info['args']):
270
self.logger.warn(
271
"Mismatched arguments for audit-event {}: {!r} != {!r}"
272
.format(name, info['args'], new_info['args'])
273
)
274
275
ids = []
276
try:
277
target = self.arguments[2].strip("\"'")
278
except (IndexError, TypeError):
279
target = None
280
if not target:
281
target = "audit_event_{}_{}".format(
282
re.sub(r'\W', '_', name),
283
len(info['source']),
284
)
285
ids.append(target)
286
287
info['source'].append((env.docname, target))
288
289
pnode = nodes.paragraph(text, classes=["audit-hook"], ids=ids)
290
pnode.line = self.lineno
291
if self.content:
292
self.state.nested_parse(self.content, self.content_offset, pnode)
293
else:
294
n, m = self.state.inline_text(text, self.lineno)
295
pnode.extend(n + m)
296
297
return [pnode]
298
299
# This list of sets are allowable synonyms for event argument names.
300
# If two names are in the same set, they are treated as equal for the
301
# purposes of warning. This won't help if number of arguments is
302
# different!
303
_SYNONYMS = [
304
{"file", "path", "fd"},
305
]
306
307
def _do_args_match(self, args1, args2):
308
if args1 == args2:
309
return True
310
if len(args1) != len(args2):
311
return False
312
for a1, a2 in zip(args1, args2):
313
if a1 == a2:
314
continue
315
if any(a1 in s and a2 in s for s in self._SYNONYMS):
316
continue
317
return False
318
return True
319
320
321
class audit_event_list(nodes.General, nodes.Element):
322
pass
323
324
325
class AuditEventListDirective(Directive):
326
327
def run(self):
328
return [audit_event_list('')]
329
330
331
# Support for documenting decorators
332
333
class PyDecoratorMixin(object):
334
def handle_signature(self, sig, signode):
335
ret = super(PyDecoratorMixin, self).handle_signature(sig, signode)
336
signode.insert(0, addnodes.desc_addname('@', '@'))
337
return ret
338
339
def needs_arglist(self):
340
return False
341
342
343
class PyDecoratorFunction(PyDecoratorMixin, PyFunction):
344
def run(self):
345
# a decorator function is a function after all
346
self.name = 'py:function'
347
return PyFunction.run(self)
348
349
350
# TODO: Use sphinx.domains.python.PyDecoratorMethod when possible
351
class PyDecoratorMethod(PyDecoratorMixin, PyMethod):
352
def run(self):
353
self.name = 'py:method'
354
return PyMethod.run(self)
355
356
357
class PyCoroutineMixin(object):
358
def handle_signature(self, sig, signode):
359
ret = super(PyCoroutineMixin, self).handle_signature(sig, signode)
360
signode.insert(0, addnodes.desc_annotation('coroutine ', 'coroutine '))
361
return ret
362
363
364
class PyAwaitableMixin(object):
365
def handle_signature(self, sig, signode):
366
ret = super(PyAwaitableMixin, self).handle_signature(sig, signode)
367
signode.insert(0, addnodes.desc_annotation('awaitable ', 'awaitable '))
368
return ret
369
370
371
class PyCoroutineFunction(PyCoroutineMixin, PyFunction):
372
def run(self):
373
self.name = 'py:function'
374
return PyFunction.run(self)
375
376
377
class PyCoroutineMethod(PyCoroutineMixin, PyMethod):
378
def run(self):
379
self.name = 'py:method'
380
return PyMethod.run(self)
381
382
383
class PyAwaitableFunction(PyAwaitableMixin, PyFunction):
384
def run(self):
385
self.name = 'py:function'
386
return PyFunction.run(self)
387
388
389
class PyAwaitableMethod(PyAwaitableMixin, PyMethod):
390
def run(self):
391
self.name = 'py:method'
392
return PyMethod.run(self)
393
394
395
class PyAbstractMethod(PyMethod):
396
397
def handle_signature(self, sig, signode):
398
ret = super(PyAbstractMethod, self).handle_signature(sig, signode)
399
signode.insert(0, addnodes.desc_annotation('abstractmethod ',
400
'abstractmethod '))
401
return ret
402
403
def run(self):
404
self.name = 'py:method'
405
return PyMethod.run(self)
406
407
408
# Support for documenting version of removal in deprecations
409
410
class DeprecatedRemoved(Directive):
411
has_content = True
412
required_arguments = 2
413
optional_arguments = 1
414
final_argument_whitespace = True
415
option_spec = {}
416
417
_deprecated_label = 'Deprecated since version {deprecated}, will be removed in version {removed}'
418
_removed_label = 'Deprecated since version {deprecated}, removed in version {removed}'
419
420
def run(self):
421
node = addnodes.versionmodified()
422
node.document = self.state.document
423
node['type'] = 'deprecated-removed'
424
version = (self.arguments[0], self.arguments[1])
425
node['version'] = version
426
env = self.state.document.settings.env
427
current_version = tuple(int(e) for e in env.config.version.split('.'))
428
removed_version = tuple(int(e) for e in self.arguments[1].split('.'))
429
if current_version < removed_version:
430
label = self._deprecated_label
431
else:
432
label = self._removed_label
433
434
label = sphinx_gettext(label)
435
text = label.format(deprecated=self.arguments[0], removed=self.arguments[1])
436
if len(self.arguments) == 3:
437
inodes, messages = self.state.inline_text(self.arguments[2],
438
self.lineno+1)
439
para = nodes.paragraph(self.arguments[2], '', *inodes, translatable=False)
440
node.append(para)
441
else:
442
messages = []
443
if self.content:
444
self.state.nested_parse(self.content, self.content_offset, node)
445
if len(node):
446
if isinstance(node[0], nodes.paragraph) and node[0].rawsource:
447
content = nodes.inline(node[0].rawsource, translatable=True)
448
content.source = node[0].source
449
content.line = node[0].line
450
content += node[0].children
451
node[0].replace_self(nodes.paragraph('', '', content, translatable=False))
452
node[0].insert(0, nodes.inline('', '%s: ' % text,
453
classes=['versionmodified']))
454
else:
455
para = nodes.paragraph('', '',
456
nodes.inline('', '%s.' % text,
457
classes=['versionmodified']),
458
translatable=False)
459
node.append(para)
460
env = self.state.document.settings.env
461
env.get_domain('changeset').note_changeset(node)
462
return [node] + messages
463
464
465
# Support for including Misc/NEWS
466
467
issue_re = re.compile('(?:[Ii]ssue #|bpo-)([0-9]+)', re.I)
468
gh_issue_re = re.compile('(?:gh-issue-|gh-)([0-9]+)', re.I)
469
whatsnew_re = re.compile(r"(?im)^what's new in (.*?)\??$")
470
471
472
class MiscNews(Directive):
473
has_content = False
474
required_arguments = 1
475
optional_arguments = 0
476
final_argument_whitespace = False
477
option_spec = {}
478
479
def run(self):
480
fname = self.arguments[0]
481
source = self.state_machine.input_lines.source(
482
self.lineno - self.state_machine.input_offset - 1)
483
source_dir = getenv('PY_MISC_NEWS_DIR')
484
if not source_dir:
485
source_dir = path.dirname(path.abspath(source))
486
fpath = path.join(source_dir, fname)
487
self.state.document.settings.record_dependencies.add(fpath)
488
try:
489
with io.open(fpath, encoding='utf-8') as fp:
490
content = fp.read()
491
except Exception:
492
text = 'The NEWS file is not available.'
493
node = nodes.strong(text, text)
494
return [node]
495
content = issue_re.sub(r':issue:`\1`', content)
496
# Fallback handling for the GitHub issue
497
content = gh_issue_re.sub(r':gh:`\1`', content)
498
content = whatsnew_re.sub(r'\1', content)
499
# remove first 3 lines as they are the main heading
500
lines = ['.. default-role:: obj', ''] + content.splitlines()[3:]
501
self.state_machine.insert_input(lines, fname)
502
return []
503
504
505
# Support for building "topic help" for pydoc
506
507
pydoc_topic_labels = [
508
'assert', 'assignment', 'async', 'atom-identifiers', 'atom-literals',
509
'attribute-access', 'attribute-references', 'augassign', 'await',
510
'binary', 'bitwise', 'bltin-code-objects', 'bltin-ellipsis-object',
511
'bltin-null-object', 'bltin-type-objects', 'booleans',
512
'break', 'callable-types', 'calls', 'class', 'comparisons', 'compound',
513
'context-managers', 'continue', 'conversions', 'customization', 'debugger',
514
'del', 'dict', 'dynamic-features', 'else', 'exceptions', 'execmodel',
515
'exprlists', 'floating', 'for', 'formatstrings', 'function', 'global',
516
'id-classes', 'identifiers', 'if', 'imaginary', 'import', 'in', 'integers',
517
'lambda', 'lists', 'naming', 'nonlocal', 'numbers', 'numeric-types',
518
'objects', 'operator-summary', 'pass', 'power', 'raise', 'return',
519
'sequence-types', 'shifting', 'slicings', 'specialattrs', 'specialnames',
520
'string-methods', 'strings', 'subscriptions', 'truth', 'try', 'types',
521
'typesfunctions', 'typesmapping', 'typesmethods', 'typesmodules',
522
'typesseq', 'typesseq-mutable', 'unary', 'while', 'with', 'yield'
523
]
524
525
526
class PydocTopicsBuilder(Builder):
527
name = 'pydoc-topics'
528
529
default_translator_class = TextTranslator
530
531
def init(self):
532
self.topics = {}
533
self.secnumbers = {}
534
535
def get_outdated_docs(self):
536
return 'all pydoc topics'
537
538
def get_target_uri(self, docname, typ=None):
539
return '' # no URIs
540
541
def write(self, *ignored):
542
writer = TextWriter(self)
543
for label in status_iterator(pydoc_topic_labels,
544
'building topics... ',
545
length=len(pydoc_topic_labels)):
546
if label not in self.env.domaindata['std']['labels']:
547
self.env.logger.warn('label %r not in documentation' % label)
548
continue
549
docname, labelid, sectname = self.env.domaindata['std']['labels'][label]
550
doctree = self.env.get_and_resolve_doctree(docname, self)
551
document = new_document('<section node>')
552
document.append(doctree.ids[labelid])
553
destination = StringOutput(encoding='utf-8')
554
writer.write(document, destination)
555
self.topics[label] = writer.output
556
557
def finish(self):
558
f = open(path.join(self.outdir, 'topics.py'), 'wb')
559
try:
560
f.write('# -*- coding: utf-8 -*-\n'.encode('utf-8'))
561
f.write(('# Autogenerated by Sphinx on %s\n' % asctime()).encode('utf-8'))
562
f.write(('topics = ' + pformat(self.topics) + '\n').encode('utf-8'))
563
finally:
564
f.close()
565
566
567
# Support for documenting Opcodes
568
569
opcode_sig_re = re.compile(r'(\w+(?:\+\d)?)(?:\s*\((.*)\))?')
570
571
572
def parse_opcode_signature(env, sig, signode):
573
"""Transform an opcode signature into RST nodes."""
574
m = opcode_sig_re.match(sig)
575
if m is None:
576
raise ValueError
577
opname, arglist = m.groups()
578
signode += addnodes.desc_name(opname, opname)
579
if arglist is not None:
580
paramlist = addnodes.desc_parameterlist()
581
signode += paramlist
582
paramlist += addnodes.desc_parameter(arglist, arglist)
583
return opname.strip()
584
585
586
# Support for documenting pdb commands
587
588
pdbcmd_sig_re = re.compile(r'([a-z()!]+)\s*(.*)')
589
590
# later...
591
# pdbargs_tokens_re = re.compile(r'''[a-zA-Z]+ | # identifiers
592
# [.,:]+ | # punctuation
593
# [\[\]()] | # parens
594
# \s+ # whitespace
595
# ''', re.X)
596
597
598
def parse_pdb_command(env, sig, signode):
599
"""Transform a pdb command signature into RST nodes."""
600
m = pdbcmd_sig_re.match(sig)
601
if m is None:
602
raise ValueError
603
name, args = m.groups()
604
fullname = name.replace('(', '').replace(')', '')
605
signode += addnodes.desc_name(name, name)
606
if args:
607
signode += addnodes.desc_addname(' '+args, ' '+args)
608
return fullname
609
610
611
def process_audit_events(app, doctree, fromdocname):
612
for node in doctree.traverse(audit_event_list):
613
break
614
else:
615
return
616
617
env = app.builder.env
618
619
table = nodes.table(cols=3)
620
group = nodes.tgroup(
621
'',
622
nodes.colspec(colwidth=30),
623
nodes.colspec(colwidth=55),
624
nodes.colspec(colwidth=15),
625
cols=3,
626
)
627
head = nodes.thead()
628
body = nodes.tbody()
629
630
table += group
631
group += head
632
group += body
633
634
row = nodes.row()
635
row += nodes.entry('', nodes.paragraph('', nodes.Text('Audit event')))
636
row += nodes.entry('', nodes.paragraph('', nodes.Text('Arguments')))
637
row += nodes.entry('', nodes.paragraph('', nodes.Text('References')))
638
head += row
639
640
for name in sorted(getattr(env, "all_audit_events", ())):
641
audit_event = env.all_audit_events[name]
642
643
row = nodes.row()
644
node = nodes.paragraph('', nodes.Text(name))
645
row += nodes.entry('', node)
646
647
node = nodes.paragraph()
648
for i, a in enumerate(audit_event['args']):
649
if i:
650
node += nodes.Text(", ")
651
node += nodes.literal(a, nodes.Text(a))
652
row += nodes.entry('', node)
653
654
node = nodes.paragraph()
655
backlinks = enumerate(sorted(set(audit_event['source'])), start=1)
656
for i, (doc, label) in backlinks:
657
if isinstance(label, str):
658
ref = nodes.reference("", nodes.Text("[{}]".format(i)), internal=True)
659
try:
660
ref['refuri'] = "{}#{}".format(
661
app.builder.get_relative_uri(fromdocname, doc),
662
label,
663
)
664
except NoUri:
665
continue
666
node += ref
667
row += nodes.entry('', node)
668
669
body += row
670
671
for node in doctree.traverse(audit_event_list):
672
node.replace_self(table)
673
674
675
def patch_pairindextypes(app, _env) -> None:
676
"""Remove all entries from ``pairindextypes`` before writing POT files.
677
678
We want to run this just before writing output files, as the check to
679
circumvent is in ``I18nBuilder.write_doc()``.
680
As such, we link this to ``env-check-consistency``, even though it has
681
nothing to do with the environment consistency check.
682
"""
683
if app.builder.name != 'gettext':
684
return
685
686
# allow translating deprecated index entries
687
try:
688
from sphinx.domains.python import pairindextypes
689
except ImportError:
690
pass
691
else:
692
# Sphinx checks if a 'pair' type entry on an index directive is one of
693
# the Sphinx-translated pairindextypes values. As we intend to move
694
# away from this, we need Sphinx to believe that these values don't
695
# exist, by deleting them when using the gettext builder.
696
pairindextypes.clear()
697
698
699
def setup(app):
700
app.add_role('issue', issue_role)
701
app.add_role('gh', gh_issue_role)
702
app.add_role('source', source_role)
703
app.add_directive('impl-detail', ImplementationDetail)
704
app.add_directive('availability', Availability)
705
app.add_directive('audit-event', AuditEvent)
706
app.add_directive('audit-event-table', AuditEventListDirective)
707
app.add_directive('deprecated-removed', DeprecatedRemoved)
708
app.add_builder(PydocTopicsBuilder)
709
app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature)
710
app.add_object_type('pdbcommand', 'pdbcmd', '%s (pdb command)', parse_pdb_command)
711
app.add_directive_to_domain('py', 'decorator', PyDecoratorFunction)
712
app.add_directive_to_domain('py', 'decoratormethod', PyDecoratorMethod)
713
app.add_directive_to_domain('py', 'coroutinefunction', PyCoroutineFunction)
714
app.add_directive_to_domain('py', 'coroutinemethod', PyCoroutineMethod)
715
app.add_directive_to_domain('py', 'awaitablefunction', PyAwaitableFunction)
716
app.add_directive_to_domain('py', 'awaitablemethod', PyAwaitableMethod)
717
app.add_directive_to_domain('py', 'abstractmethod', PyAbstractMethod)
718
app.add_directive('miscnews', MiscNews)
719
app.connect('env-check-consistency', patch_pairindextypes)
720
app.connect('doctree-resolved', process_audit_events)
721
app.connect('env-merge-info', audit_events_merge)
722
app.connect('env-purge-doc', audit_events_purge)
723
return {'version': '1.0', 'parallel_read_safe': True}
724
725