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