| # SPDX-FileCopyrightText: 2023 Guido Günther | |
| # base on # codegen_rst.py (C) 2022 Emmanuele Bassi | |
| # | |
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| import os | |
| import re | |
| import textwrap | |
| # Disable line length warnings as wrapping the templates would be hard | |
| # flake8: noqa: E501 | |
| class MdCodeGenerator: | |
| """Generates documentation in Markdown format.""" | |
| def __init__(self, ifaces): | |
| self.ifaces = ifaces | |
| self._generate_expand_dicts() | |
| def _expand(self, s, expandParamsAndConstants): | |
| """Expands parameters and constant literals.""" | |
| res = [] | |
| for line in textwrap.dedent(s).split("\n"): | |
| line = line.rstrip() | |
| if line == "": | |
| res.append("") | |
| continue | |
| for key in self._expand_member_dict_keys: | |
| line = line.replace(key, self._expand_member_dict[key]) | |
| for key in self._expand_iface_dict_keys: | |
| line = line.replace(key, self._expand_iface_dict[key]) | |
| if expandParamsAndConstants: | |
| # replace @foo with `foo` | |
| line = re.sub( | |
| "@[a-zA-Z0-9_]*", | |
| lambda m: "`" + m.group(0)[1:] + "`", | |
| line, | |
| ) | |
| # replace e.g. %TRUE with ``TRUE`` | |
| line = re.sub( | |
| "%[a-zA-Z0-9_]*", | |
| lambda m: "`" + m.group(0)[1:] + "`", | |
| line, | |
| ) | |
| res.append(line) | |
| return "\n".join(res) | |
| def _generate_expand_dicts(self): | |
| """Generates the dictionaries used to expand gtk-doc sigils.""" | |
| self._expand_member_dict = {} | |
| self._expand_iface_dict = {} | |
| for i in self.ifaces: | |
| key = f"#{i.name}" | |
| value = f"`{i.name}`_" | |
| self._expand_iface_dict[key] = value | |
| for m in i.methods: | |
| key = "%s.%s()" % (i.name, m.name) | |
| value = f"`{i.name}.{m.name}`_" | |
| self._expand_member_dict[key] = value | |
| for s in i.signals: | |
| key = "#%s::%s" % (i.name, s.name) | |
| value = f"`{i.name}::{s.name}`_" | |
| self._expand_member_dict[key] = value | |
| for p in i.properties: | |
| key = "#%s:%s" % (i.name, p.name) | |
| value = f"`{i.name}:{p.name}`_" | |
| self._expand_member_dict[key] = value | |
| # Make sure to expand the keys in reverse order so e.g. #org.foo.Iface:MediaCompat | |
| # is evaluated before #org.foo.Iface:Media ... | |
| self._expand_member_dict_keys = sorted( | |
| self._expand_member_dict.keys(), reverse=True | |
| ) | |
| self._expand_iface_dict_keys = sorted( | |
| self._expand_iface_dict.keys(), reverse=True | |
| ) | |
| def _generate_header(self, iface): | |
| """Generates the header and preamble of the document.""" | |
| header_len = len(iface.name) | |
| res = [ | |
| f"Title: {iface.name} D-Bus Interface", | |
| f"Slug: {iface.name}", | |
| "", | |
| "# " + iface.name_without_prefix, | |
| "", | |
| "## Description", | |
| "", | |
| iface.doc_string_brief.strip(), | |
| "", | |
| self._expand(iface.doc_string, True), | |
| "", | |
| ] | |
| if iface.since: | |
| res += [ | |
| f"Interface available since: {iface.since}.", | |
| "", | |
| ] | |
| if iface.deprecated: | |
| res += [ | |
| "*Warning*: This interface is deprecated.", | |
| "", | |
| ] | |
| res += [""] | |
| return "\n".join(res) | |
| def _generate_section(self, title, name): | |
| """Generates a section with the given title.""" | |
| res = [ | |
| "### " + title, | |
| "", | |
| ] | |
| return "\n".join(res) | |
| def _generate_properties(self, iface): | |
| """Generates the properties section.""" | |
| res = [] | |
| for p in iface.properties: | |
| title = f"{iface.name}:{p.name}" | |
| if p.readable and p.writable: | |
| access = "readwrite" | |
| elif p.writable: | |
| access = "writable" | |
| else: | |
| access = "readable" | |
| res += [ | |
| "### " + title, | |
| "", | |
| "```", | |
| f" {p.name} {access} {p.signature}", | |
| "```", | |
| "", | |
| self._expand(p.doc_string, True), | |
| "", | |
| ] | |
| if p.since: | |
| res += [ | |
| f"Property available since: {p.since}.", | |
| "", | |
| ] | |
| if p.deprecated: | |
| res += [ | |
| "*Warning*: This property is deprecated.", | |
| "", | |
| ] | |
| res += [""] | |
| return "\n".join(res) | |
| def _generate_method_signature(self, method): | |
| """Generates the method signature as a code block.""" | |
| res = [ | |
| "```", | |
| ] | |
| n_in_args = len(method.in_args) | |
| n_out_args = len(method.out_args) | |
| if n_in_args == 0 and n_out_args == 0: | |
| res += [ | |
| f" {method.name} ()", | |
| ] | |
| else: | |
| res += [ | |
| f" {method.name} (", | |
| ] | |
| for idx, arg in enumerate(method.in_args): | |
| if idx == n_in_args - 1 and n_out_args == 0: | |
| res += [ | |
| f" IN {arg.name} {arg.signature}", | |
| ] | |
| else: | |
| res += [ | |
| f" IN {arg.name} {arg.signature},", | |
| ] | |
| for idx, arg in enumerate(method.out_args): | |
| if idx == n_out_args - 1: | |
| res += [ | |
| f" OUT {arg.name} {arg.signature}", | |
| ] | |
| else: | |
| res += [ | |
| f" OUT {arg.name} {arg.signature},", | |
| ] | |
| res += [ | |
| " )", | |
| ] | |
| res += ["```"] | |
| return "\n".join(res) | |
| def _generate_methods(self, iface): | |
| """Generates the methods section.""" | |
| res = [] | |
| for m in iface.methods: | |
| title = f"{iface.name}.{m.name}" | |
| res += [ | |
| "### " + title, | |
| "", | |
| self._generate_method_signature(m), | |
| "", | |
| self._expand(m.doc_string, True), | |
| "", | |
| ] | |
| for a in m.in_args: | |
| arg_desc = self._expand(a.doc_string, True) | |
| res += [ | |
| f"* {a.name}: {arg_desc}", | |
| "", | |
| ] | |
| res += [""] | |
| if m.since: | |
| res += [ | |
| f"Method available since: {m.since}.", | |
| "", | |
| ] | |
| if m.deprecated: | |
| res += [ | |
| "*Warning*: This method is deprecated.", | |
| "", | |
| ] | |
| res += [""] | |
| return "\n".join(res) | |
| def _generate_signal_signature(self, signal): | |
| """Generates the signal signature.""" | |
| res = [ | |
| "```", | |
| ] | |
| n_args = len(signal.args) | |
| if n_args == 0: | |
| res += [ | |
| f" {signal.name} ()", | |
| ] | |
| else: | |
| res += [ | |
| f" {signal.name} (", | |
| ] | |
| for idx, arg in enumerate(signal.args): | |
| if idx == n_args - 1: | |
| res += [ | |
| f" {arg.name} {arg.signature}", | |
| ] | |
| else: | |
| res += [ | |
| f" {arg.name} {arg.signature},", | |
| ] | |
| res += [ | |
| " )", | |
| ] | |
| res += ["```"] | |
| return "\n".join(res) | |
| def _generate_signals(self, iface): | |
| """Generates the signals section.""" | |
| res = [] | |
| for s in iface.signals: | |
| title = f"{iface.name}::{s.name}" | |
| res += [ | |
| "### " + title, | |
| "", | |
| self._generate_signal_signature(s), | |
| "", | |
| self._expand(s.doc_string, True), | |
| "", | |
| ] | |
| for a in s.args: | |
| arg_desc = self._expand(a.doc_string, True) | |
| res += [ | |
| f"{a.name}", | |
| f" {arg_desc}", | |
| "", | |
| ] | |
| res += [""] | |
| if s.since: | |
| res += [ | |
| f"Signal available since: {s.since}.", | |
| "", | |
| ] | |
| if s.deprecated: | |
| res += [ | |
| "*Warning*: This signal is deprecated.", | |
| "", | |
| ] | |
| res += [""] | |
| return "\n".join(res) | |
| def generate(self, md, outdir): | |
| """Generates the Markdown file for each interface.""" | |
| for i in self.ifaces: | |
| with open(os.path.join(outdir, f"{md}-{i.name}.md"), "w") as outfile: | |
| outfile.write(self._generate_header(i)) | |
| if len(i.properties) > 0: | |
| outfile.write(self._generate_section("Properties", i.name)) | |
| outfile.write(self._generate_properties(i)) | |
| if len(i.methods) > 0: | |
| outfile.write(self._generate_section("Methods", i.name)) | |
| outfile.write(self._generate_methods(i)) | |
| if len(i.signals) > 0: | |
| outfile.write(self._generate_section("Signals", i.name)) | |
| outfile.write(self._generate_signals(i)) |