Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Doc/tools/extensions/c_annotations.py
12 views
1
# -*- coding: utf-8 -*-
2
"""
3
c_annotations.py
4
~~~~~~~~~~~~~~~~
5
6
Supports annotations for C API elements:
7
8
* reference count annotations for C API functions. Based on
9
refcount.py and anno-api.py in the old Python documentation tools.
10
11
* stable API annotations
12
13
Usage:
14
* Set the `refcount_file` config value to the path to the reference
15
count data file.
16
* Set the `stable_abi_file` config value to the path to stable ABI list.
17
18
:copyright: Copyright 2007-2014 by Georg Brandl.
19
:license: Python license.
20
"""
21
22
from os import path
23
import docutils
24
from docutils import nodes
25
from docutils.parsers.rst import directives
26
from docutils.parsers.rst import Directive
27
from docutils.statemachine import StringList
28
from sphinx.locale import _ as sphinx_gettext
29
import csv
30
31
from sphinx import addnodes
32
from sphinx.domains.c import CObject
33
34
35
REST_ROLE_MAP = {
36
'function': 'func',
37
'var': 'data',
38
'type': 'type',
39
'macro': 'macro',
40
'type': 'type',
41
'member': 'member',
42
}
43
44
45
# Monkeypatch nodes.Node.findall for forwards compatability
46
# This patch can be dropped when the minimum Sphinx version is 4.4.0
47
# or the minimum Docutils version is 0.18.1.
48
if docutils.__version_info__ < (0, 18, 1):
49
def findall(self, *args, **kwargs):
50
return iter(self.traverse(*args, **kwargs))
51
52
nodes.Node.findall = findall
53
54
55
class RCEntry:
56
def __init__(self, name):
57
self.name = name
58
self.args = []
59
self.result_type = ''
60
self.result_refs = None
61
62
63
class Annotations:
64
def __init__(self, refcount_filename, stable_abi_file):
65
self.refcount_data = {}
66
with open(refcount_filename, 'r') as fp:
67
for line in fp:
68
line = line.strip()
69
if line[:1] in ("", "#"):
70
# blank lines and comments
71
continue
72
parts = line.split(":", 4)
73
if len(parts) != 5:
74
raise ValueError("Wrong field count in %r" % line)
75
function, type, arg, refcount, comment = parts
76
# Get the entry, creating it if needed:
77
try:
78
entry = self.refcount_data[function]
79
except KeyError:
80
entry = self.refcount_data[function] = RCEntry(function)
81
if not refcount or refcount == "null":
82
refcount = None
83
else:
84
refcount = int(refcount)
85
# Update the entry with the new parameter or the result
86
# information.
87
if arg:
88
entry.args.append((arg, type, refcount))
89
else:
90
entry.result_type = type
91
entry.result_refs = refcount
92
93
self.stable_abi_data = {}
94
with open(stable_abi_file, 'r') as fp:
95
for record in csv.DictReader(fp):
96
role = record['role']
97
name = record['name']
98
self.stable_abi_data[name] = record
99
100
def add_annotations(self, app, doctree):
101
for node in doctree.findall(addnodes.desc_content):
102
par = node.parent
103
if par['domain'] != 'c':
104
continue
105
if not par[0].has_key('ids') or not par[0]['ids']:
106
continue
107
name = par[0]['ids'][0]
108
if name.startswith("c."):
109
name = name[2:]
110
111
objtype = par['objtype']
112
113
# Stable ABI annotation. These have two forms:
114
# Part of the [Stable ABI](link).
115
# Part of the [Stable ABI](link) since version X.Y.
116
# For structs, there's some more info in the message:
117
# Part of the [Limited API](link) (as an opaque struct).
118
# Part of the [Stable ABI](link) (including all members).
119
# Part of the [Limited API](link) (Only some members are part
120
# of the stable ABI.).
121
# ... all of which can have "since version X.Y" appended.
122
record = self.stable_abi_data.get(name)
123
if record:
124
if record['role'] != objtype:
125
raise ValueError(
126
f"Object type mismatch in limited API annotation "
127
f"for {name}: {record['role']!r} != {objtype!r}")
128
stable_added = record['added']
129
message = ' Part of the '
130
emph_node = nodes.emphasis(message, message,
131
classes=['stableabi'])
132
ref_node = addnodes.pending_xref(
133
'Stable ABI', refdomain="std", reftarget='stable',
134
reftype='ref', refexplicit="False")
135
struct_abi_kind = record['struct_abi_kind']
136
if struct_abi_kind in {'opaque', 'members'}:
137
ref_node += nodes.Text('Limited API')
138
else:
139
ref_node += nodes.Text('Stable ABI')
140
emph_node += ref_node
141
if struct_abi_kind == 'opaque':
142
emph_node += nodes.Text(' (as an opaque struct)')
143
elif struct_abi_kind == 'full-abi':
144
emph_node += nodes.Text(' (including all members)')
145
if record['ifdef_note']:
146
emph_node += nodes.Text(' ' + record['ifdef_note'])
147
if stable_added == '3.2':
148
# Stable ABI was introduced in 3.2.
149
pass
150
else:
151
emph_node += nodes.Text(f' since version {stable_added}')
152
emph_node += nodes.Text('.')
153
if struct_abi_kind == 'members':
154
emph_node += nodes.Text(
155
' (Only some members are part of the stable ABI.)')
156
node.insert(0, emph_node)
157
158
# Unstable API annotation.
159
if name.startswith('PyUnstable'):
160
warn_node = nodes.admonition(
161
classes=['unstable-c-api', 'warning'])
162
message = 'This is '
163
emph_node = nodes.emphasis(message, message)
164
ref_node = addnodes.pending_xref(
165
'Unstable API', refdomain="std",
166
reftarget='unstable-c-api',
167
reftype='ref', refexplicit="False")
168
ref_node += nodes.Text('Unstable API')
169
emph_node += ref_node
170
emph_node += nodes.Text('. It may change without warning in minor releases.')
171
warn_node += emph_node
172
node.insert(0, warn_node)
173
174
# Return value annotation
175
if objtype != 'function':
176
continue
177
entry = self.refcount_data.get(name)
178
if not entry:
179
continue
180
elif not entry.result_type.endswith("Object*"):
181
continue
182
if entry.result_refs is None:
183
rc = sphinx_gettext('Return value: Always NULL.')
184
elif entry.result_refs:
185
rc = sphinx_gettext('Return value: New reference.')
186
else:
187
rc = sphinx_gettext('Return value: Borrowed reference.')
188
node.insert(0, nodes.emphasis(rc, rc, classes=['refcount']))
189
190
191
def init_annotations(app):
192
annotations = Annotations(
193
path.join(app.srcdir, app.config.refcount_file),
194
path.join(app.srcdir, app.config.stable_abi_file),
195
)
196
app.connect('doctree-read', annotations.add_annotations)
197
198
class LimitedAPIList(Directive):
199
200
has_content = False
201
required_arguments = 0
202
optional_arguments = 0
203
final_argument_whitespace = True
204
205
def run(self):
206
content = []
207
for record in annotations.stable_abi_data.values():
208
role = REST_ROLE_MAP[record['role']]
209
name = record['name']
210
content.append(f'* :c:{role}:`{name}`')
211
212
pnode = nodes.paragraph()
213
self.state.nested_parse(StringList(content), 0, pnode)
214
return [pnode]
215
216
app.add_directive('limited-api-list', LimitedAPIList)
217
218
219
def setup(app):
220
app.add_config_value('refcount_file', '', True)
221
app.add_config_value('stable_abi_file', '', True)
222
app.connect('builder-inited', init_annotations)
223
224
# monkey-patch C object...
225
CObject.option_spec = {
226
'noindex': directives.flag,
227
'stableabi': directives.flag,
228
}
229
old_handle_signature = CObject.handle_signature
230
def new_handle_signature(self, sig, signode):
231
signode.parent['stableabi'] = 'stableabi' in self.options
232
return old_handle_signature(self, sig, signode)
233
CObject.handle_signature = new_handle_signature
234
return {'version': '1.0', 'parallel_read_safe': True}
235
236