glib (2.79.0)
1 # SPDX-FileCopyrightText: 2022 Emmanuele Bassi
2 #
3 # SPDX-License-Identifier: LGPL-2.1-or-later
4
5 import os
6 import re
7
8 from . import utils
9 import textwrap
10
11 # Disable line length warnings as wrapping the templates would be hard
12 # flake8: noqa: E501
13
14
15 class ESC[4;38;5;81mRstCodeGenerator:
16 """Generates documentation in reStructuredText format."""
17
18 def __init__(self, ifaces):
19 self.ifaces = ifaces
20 self._generate_expand_dicts()
21
22 def _expand(self, s, expandParamsAndConstants):
23 """Expands parameters and constant literals."""
24 res = []
25 for line in textwrap.dedent(s).split("\n"):
26 line = line.rstrip()
27 if line == "":
28 res.append("")
29 continue
30 for key in self._expand_member_dict_keys:
31 line = line.replace(key, self._expand_member_dict[key])
32 for key in self._expand_iface_dict_keys:
33 line = line.replace(key, self._expand_iface_dict[key])
34 if expandParamsAndConstants:
35 # replace @foo with ``foo``
36 line = re.sub(
37 "@[a-zA-Z0-9_]*",
38 lambda m: "``" + m.group(0)[1:] + "``",
39 line,
40 )
41 # replace e.g. %TRUE with ``TRUE``
42 line = re.sub(
43 "%[a-zA-Z0-9_]*",
44 lambda m: "``" + m.group(0)[1:] + "``",
45 line,
46 )
47 res.append(line)
48 return "\n".join(res)
49
50 def _generate_expand_dicts(self):
51 """Generates the dictionaries used to expand gtk-doc sigils."""
52 self._expand_member_dict = {}
53 self._expand_iface_dict = {}
54 for i in self.ifaces:
55 key = f"#{i.name}"
56 value = f"`{i.name}`_"
57 self._expand_iface_dict[key] = value
58
59 for m in i.methods:
60 key = "%s.%s()" % (i.name, m.name)
61 value = f"`{i.name}.{m.name}`_"
62 self._expand_member_dict[key] = value
63
64 for s in i.signals:
65 key = "#%s::%s" % (i.name, s.name)
66 value = f"`{i.name}::{s.name}`_"
67 self._expand_member_dict[key] = value
68
69 for p in i.properties:
70 key = "#%s:%s" % (i.name, p.name)
71 value = f"`{i.name}:{p.name}`_"
72 self._expand_member_dict[key] = value
73
74 # Make sure to expand the keys in reverse order so e.g. #org.foo.Iface:MediaCompat
75 # is evaluated before #org.foo.Iface:Media ...
76 self._expand_member_dict_keys = sorted(
77 self._expand_member_dict.keys(), reverse=True
78 )
79 self._expand_iface_dict_keys = sorted(
80 self._expand_iface_dict.keys(), reverse=True
81 )
82
83 def _generate_header(self, iface):
84 """Generates the header and preamble of the document."""
85 header_len = len(iface.name)
86 res = [
87 f".. _{iface.name}:",
88 "",
89 "=" * header_len,
90 iface.name,
91 "=" * header_len,
92 "",
93 "-----------",
94 "Description",
95 "-----------",
96 "",
97 f".. _{iface.name} Description:",
98 "",
99 iface.doc_string_brief.strip(),
100 "",
101 self._expand(iface.doc_string, True),
102 "",
103 ]
104 if iface.since:
105 res += [
106 f"Interface available since: {iface.since}.",
107 "",
108 ]
109 if iface.deprecated:
110 res += [
111 ".. warning::",
112 "",
113 " This interface is deprecated.",
114 "",
115 "",
116 ]
117 res += [""]
118 return "\n".join(res)
119
120 def _generate_section(self, title, name):
121 """Generates a section with the given title."""
122 res = [
123 f".. _{name} {title}:",
124 "",
125 "-" * len(title),
126 title,
127 "-" * len(title),
128 "",
129 "",
130 ]
131 return "\n".join(res)
132
133 def _generate_properties(self, iface):
134 """Generates the properties section."""
135 res = []
136 for p in iface.properties:
137 title = f"{iface.name}:{p.name}"
138 if p.readable and p.writable:
139 access = "readwrite"
140 elif p.writable:
141 access = "writable"
142 else:
143 access = "readable"
144 res += [
145 f".. _{title}:",
146 "",
147 title,
148 "^" * len(title),
149 "",
150 "::",
151 "",
152 f" {p.name} {access} {p.signature}",
153 "",
154 "",
155 self._expand(p.doc_string, True),
156 "",
157 ]
158 if p.since:
159 res += [
160 f"Property available since: {p.since}.",
161 "",
162 ]
163 if p.deprecated:
164 res += [
165 ".. warning::",
166 "",
167 " This property is deprecated.",
168 "",
169 "",
170 ]
171 res += [""]
172 return "\n".join(res)
173
174 def _generate_method_signature(self, method):
175 """Generates the method signature as a code block."""
176 res = [
177 "::",
178 "",
179 ]
180 n_in_args = len(method.in_args)
181 n_out_args = len(method.out_args)
182 if n_in_args == 0 and n_out_args == 0:
183 res += [
184 f" {method.name} ()",
185 ]
186 else:
187 res += [
188 f" {method.name} (",
189 ]
190 for idx, arg in enumerate(method.in_args):
191 if idx == n_in_args - 1 and n_out_args == 0:
192 res += [
193 f" IN {arg.name} {arg.signature}",
194 ]
195 else:
196 res += [
197 f" IN {arg.name} {arg.signature},",
198 ]
199 for idx, arg in enumerate(method.out_args):
200 if idx == n_out_args - 1:
201 res += [
202 f" OUT {arg.name} {arg.signature}",
203 ]
204 else:
205 res += [
206 f" OUT {arg.name} {arg.signature},",
207 ]
208 res += [
209 " )",
210 "",
211 ]
212 res += [""]
213 return "\n".join(res)
214
215 def _generate_methods(self, iface):
216 """Generates the methods section."""
217 res = []
218 for m in iface.methods:
219 title = f"{iface.name}.{m.name}"
220 res += [
221 f".. _{title}:",
222 "",
223 title,
224 "^" * len(title),
225 "",
226 self._generate_method_signature(m),
227 "",
228 self._expand(m.doc_string, True),
229 "",
230 ]
231 for a in m.in_args:
232 arg_desc = self._expand(a.doc_string, True)
233 res += [
234 f"{a.name}",
235 f" {arg_desc}",
236 "",
237 ]
238 for a in m.out_args:
239 arg_desc = self._expand(a.doc_string, True)
240 res += [
241 f"{a.name}",
242 f" {arg_desc}",
243 "",
244 ]
245 res += [""]
246 if m.since:
247 res += [
248 f"Method available since: {m.since}.",
249 "",
250 ]
251 if m.deprecated:
252 res += [
253 ".. warning::",
254 "",
255 " This method is deprecated.",
256 "",
257 "",
258 ]
259 res += [""]
260 return "\n".join(res)
261
262 def _generate_signal_signature(self, signal):
263 """Generates the signal signature."""
264 res = [
265 "::",
266 "",
267 ]
268 n_args = len(signal.args)
269 if n_args == 0:
270 res += [
271 f" {signal.name} ()",
272 ]
273 else:
274 res += [
275 f" {signal.name} (",
276 ]
277 for idx, arg in enumerate(signal.args):
278 if idx == n_args - 1:
279 res += [
280 f" {arg.name} {arg.signature}",
281 ]
282 else:
283 res += [
284 f" {arg.name} {arg.signature},",
285 ]
286 res += [
287 " )",
288 "",
289 ]
290 res += [""]
291 return "\n".join(res)
292
293 def _generate_signals(self, iface):
294 """Generates the signals section."""
295 res = []
296 for s in iface.signals:
297 title = f"{iface.name}::{s.name}"
298 res += [
299 f".. _{title}:",
300 "",
301 title,
302 "^" * len(title),
303 "",
304 self._generate_signal_signature(s),
305 "",
306 self._expand(s.doc_string, True),
307 "",
308 ]
309 for a in s.args:
310 arg_desc = self._expand(a.doc_string, True)
311 res += [
312 f"{a.name}",
313 f" {arg_desc}",
314 "",
315 ]
316 res += [""]
317 if s.since:
318 res += [
319 f"Signal available since: {s.since}.",
320 "",
321 ]
322 if s.deprecated:
323 res += [
324 ".. warning::",
325 "",
326 " This signal is deprecated.",
327 "",
328 "",
329 ]
330 res += [""]
331 return "\n".join(res)
332
333 def generate(self, rst, outdir):
334 """Generates the reStructuredText file for each interface."""
335 for i in self.ifaces:
336 with open(os.path.join(outdir, f"{rst}-{i.name}.rst"), "w") as outfile:
337 outfile.write(self._generate_header(i))
338 if len(i.properties) > 0:
339 outfile.write(self._generate_section("Properties", i.name))
340 outfile.write(self._generate_properties(i))
341 if len(i.methods) > 0:
342 outfile.write(self._generate_section("Methods", i.name))
343 outfile.write(self._generate_methods(i))
344 if len(i.signals) > 0:
345 outfile.write(self._generate_section("Signals", i.name))
346 outfile.write(self._generate_signals(i))