| # SPDX-FileCopyrightText: 2022 Emmanuele Bassi |
| # |
| # SPDX-License-Identifier: LGPL-2.1-or-later |
| |
| import os |
| import re |
| |
| from . import utils |
| |
| # 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 s.split("\n"): |
| line = line.strip() |
| 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".. _{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 = [ |
| "-" * len(title), |
| title, |
| "-" * len(title), |
| "", |
| f".. {name} {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, |
| "^" * 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 += [ |
| 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}", |
| "", |
| ] |
| 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, |
| "^" * 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)) |