[glib/ebassi/gdbus-codegen-rst: 6/8] Add reStructuredText generator to gdbus-codegen




commit 4591c83e6c761b3c6ff17ce397b17a7b848c064d
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Wed Jan 19 13:55:29 2022 +0000

    Add reStructuredText generator to gdbus-codegen
    
    The gdbus-codegen tool generates documentation from the XML introspection
    description of a D-Bus interface. Currently, only DocBook is supported at
    the moment, but not every modern documentation generator can handle that
    format. The reStructuredText format is a bit more well-supported,
    especially in documentation generators for non-C languages.
    
    Unlike DocBook, we get to make our own structure and conventions for how
    we structure the documentation when using reStructuredText.

 gio/gdbus-2.0/codegen/codegen_main.py |  17 +-
 gio/gdbus-2.0/codegen/codegen_rst.py  | 332 ++++++++++++++++++++++++++++++++++
 gio/gdbus-2.0/codegen/meson.build     |   1 +
 3 files changed, 348 insertions(+), 2 deletions(-)
---
diff --git a/gio/gdbus-2.0/codegen/codegen_main.py b/gio/gdbus-2.0/codegen/codegen_main.py
index 238d7dd12..194800c78 100644
--- a/gio/gdbus-2.0/codegen/codegen_main.py
+++ b/gio/gdbus-2.0/codegen/codegen_main.py
@@ -30,6 +30,7 @@ from . import dbustypes
 from . import parser
 from . import codegen
 from . import codegen_docbook
+from . import codegen_rst
 from .utils import print_error, print_warning
 
 
@@ -211,6 +212,11 @@ def codegen_main():
         metavar="OUTFILES",
         help="Generate Docbook in OUTFILES-org.Project.IFace.xml",
     )
+    arg_parser.add_argument(
+        "--generate-rst",
+        metavar="OUTFILES",
+        help="Generate reStructuredText in OUTFILES-org.Project.IFace.rst",
+    )
     arg_parser.add_argument(
         "--pragma-once",
         action="store_true",
@@ -287,10 +293,12 @@ def codegen_main():
         )
 
     if (
-        args.generate_c_code is not None or args.generate_docbook is not None
+        args.generate_c_code is not None
+        or args.generate_docbook is not None
+        or args.generate_rst is not None
     ) and args.output is not None:
         print_error(
-            "Using --generate-c-code or --generate-docbook and "
+            "Using --generate-c-code or --generate-docbook or --generate-rst and "
             "--output at the same time is not allowed"
         )
 
@@ -420,6 +428,11 @@ def codegen_main():
     if docbook:
         docbook_gen.generate(docbook, args.output_directory)
 
+    rst = args.generate_rst
+    rst_gen = codegen_rst.RstCodeGenerator(all_ifaces)
+    if rst:
+        rst_gen.generate(rst, args.output_directory)
+
     if args.header:
         with open(h_file, "w") as outfile:
             gen = codegen.HeaderCodeGenerator(
diff --git a/gio/gdbus-2.0/codegen/codegen_rst.py b/gio/gdbus-2.0/codegen/codegen_rst.py
new file mode 100644
index 000000000..51da2d572
--- /dev/null
+++ b/gio/gdbus-2.0/codegen/codegen_rst.py
@@ -0,0 +1,332 @@
+# 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))
diff --git a/gio/gdbus-2.0/codegen/meson.build b/gio/gdbus-2.0/codegen/meson.build
index c0caf0e50..bf25cdaeb 100644
--- a/gio/gdbus-2.0/codegen/meson.build
+++ b/gio/gdbus-2.0/codegen/meson.build
@@ -3,6 +3,7 @@ gdbus_codegen_files = [
   'codegen.py',
   'codegen_main.py',
   'codegen_docbook.py',
+  'codegen_rst.py',
   'dbustypes.py',
   'parser.py',
   'utils.py',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]