# SPDX-FileCopyrightText: 2022 Emmanuele Bassi # # SPDX-License-Identifier: LGPL-2.1-or-later import os import re from . import utils import textwrap # Disable line length warnings as wrapping the templates would be hard # flake8: noqa: E501 class RstCodeGenerator: """Generates documentation in reStructuredText 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.""" iface_name = iface.name_without_prefix header_len = len(iface_name) res = [ f".. _{iface.name}:", "", "=" * header_len, iface_name, "=" * header_len, "", "-----------", "Description", "-----------", "", f".. _{iface.name} 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 = [ f".. _{name} {title}:", "", "-" * len(title), title, "-" * len(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 += [ f".. _{title}:", "", title, "^" * len(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 += [ f".. _{title}:", "", title, "^" * len(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}", f" {arg_desc}", "", ] for a in m.out_args: arg_desc = self._expand(a.doc_string, True) res += [ f"{a.name}", f" {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 += [ f".. _{title}:", "", title, "^" * len(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, rst, outdir): """Generates the reStructuredText file for each interface.""" for i in self.ifaces: with open(os.path.join(outdir, f"{rst}-{i.name}.rst"), "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))