diff --git a/docs/reference/gio/gdbus-codegen.xml b/docs/reference/gio/gdbus-codegen.xml
index 960b5ffa0..5860fed97 100644
--- a/docs/reference/gio/gdbus-codegen.xml
+++ b/docs/reference/gio/gdbus-codegen.xml
@@ -35,6 +35,7 @@
none|objects|all
OUTDIR
OUTFILES
+ OUTFILES
FILE
@@ -168,6 +169,16 @@
+
+ Generating reStructuredText documentation
+
+ Each generated reStructuredText file (see the
+ option for details) is a plain text
+ reStructuredText
+ document describing the D-Bus interface.
+
+
+
Options
@@ -212,8 +223,25 @@
Generate Docbook Documentation for each D-Bus interface and
- put it in OUTFILES-NAME.xml where
- NAME is a place-holder for the interface
+ put it in OUTFILES-NAME.xml
+ where NAME is a place-holder for the interface
+ name, e.g. net.Corp.FooBar and so on.
+
+
+ Pass to specify the directory
+ to put the output files in. By default the current directory
+ will be used.
+
+
+
+
+
+ OUTFILES
+
+
+ Generate reStructuredText Documentation for each D-Bus interface and
+ put it in OUTFILES-NAME.rst
+ where NAME is a place-holder for the interface
name, e.g. net.Corp.FooBar and so on.
diff --git a/gio/gdbus-2.0/codegen/codegen_docbook.py b/gio/gdbus-2.0/codegen/codegen_docbook.py
index 4b69e2927..b8d683408 100644
--- a/gio/gdbus-2.0/codegen/codegen_docbook.py
+++ b/gio/gdbus-2.0/codegen/codegen_docbook.py
@@ -345,9 +345,17 @@ class DocbookCodeGenerator:
def expand_paras(self, s, expandParamsAndConstants):
s = self.expand(s, expandParamsAndConstants).strip()
+ res = []
if not s.startswith("")
+ for line in s.split("\n"):
+ line = line.strip()
+ if not line:
+ line = ""
+ res.append(line)
+ if not s.endswith(""):
+ res.append("")
+ return "\n".join(res)
def generate_expand_dicts(self):
self.expand_member_dict = {}
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',
diff --git a/gio/gdbus-2.0/codegen/parser.py b/gio/gdbus-2.0/codegen/parser.py
index 45226d540..cf8ea5229 100644
--- a/gio/gdbus-2.0/codegen/parser.py
+++ b/gio/gdbus-2.0/codegen/parser.py
@@ -85,7 +85,7 @@ class DBusXMLParser:
symbol = line[0:colon_index]
rest_of_line = line[colon_index + 2 :].strip()
if len(rest_of_line) > 0:
- body += "" + rest_of_line + ""
+ body += f"{rest_of_line}\n"
comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS:
if line.startswith("@"):
@@ -93,9 +93,9 @@ class DBusXMLParser:
if colon_index == -1:
comment_state = DBusXMLParser.COMMENT_STATE_BODY
if not in_para:
- body += ""
+ body += "\n"
in_para = True
- body += orig_line + "\n"
+ body += f"{orig_line}\n"
else:
param = line[1:colon_index]
docs = line[colon_index + 2 :]
@@ -104,21 +104,20 @@ class DBusXMLParser:
comment_state = DBusXMLParser.COMMENT_STATE_BODY
if len(line) > 0:
if not in_para:
- body += ""
+ body += "\n"
in_para = True
body += orig_line + "\n"
elif comment_state == DBusXMLParser.COMMENT_STATE_BODY:
if len(line) > 0:
if not in_para:
- body += ""
in_para = True
body += orig_line + "\n"
else:
if in_para:
- body += ""
+ body += "\n"
in_para = False
if in_para:
- body += ""
+ body += "\n"
if symbol != "":
self.doc_comment_last_symbol = symbol
diff --git a/gio/tests/codegen.py b/gio/tests/codegen.py
index 031776537..c95736e39 100644
--- a/gio/tests/codegen.py
+++ b/gio/tests/codegen.py
@@ -382,6 +382,46 @@ G_END_DECLS
# The output should be the same.
self.assertEqual(result1.out, result2.out)
+ def test_generate_docbook(self):
+ """Test the basic functionality of the docbook generator."""
+ xml_contents = """
+
+
+
+
+
+ """
+ res = self.runCodegenWithInterface(
+ xml_contents,
+ "--generate-docbook",
+ "test",
+ )
+ self.assertEqual("", res.err)
+ self.assertEqual("", res.out)
+ with open("test-org.project.Bar.Frobnicator.xml", "r") as f:
+ xml_data = f.readlines()
+ self.assertTrue(len(xml_data) != 0)
+
+ def test_generate_rst(self):
+ """Test the basic functionality of the rst generator."""
+ xml_contents = """
+
+
+
+
+
+ """
+ res = self.runCodegenWithInterface(
+ xml_contents,
+ "--generate-rst",
+ "test",
+ )
+ self.assertEqual("", res.err)
+ self.assertEqual("", res.out)
+ with open("test-org.project.Bar.Frobnicator.rst", "r") as f:
+ rst = f.readlines()
+ self.assertTrue(len(rst) != 0)
+
def test_glib_min_required_invalid(self):
"""Test running with an invalid --glib-min-required."""
with self.assertRaises(subprocess.CalledProcessError):
diff --git a/gio/tests/gdbus-object-manager-example/meson.build b/gio/tests/gdbus-object-manager-example/meson.build
index f9c3bce26..ce0335e11 100644
--- a/gio/tests/gdbus-object-manager-example/meson.build
+++ b/gio/tests/gdbus-object-manager-example/meson.build
@@ -17,6 +17,22 @@ gdbus_example_objectmanager_generated = custom_target('objectmanager-gen',
'--symbol-decorator-define', 'HAVE_CONFIG_H',
'@INPUT@'])
+gdbus_example_objectmanager_rst_gen = custom_target('objectmanager-rst-gen',
+ input: gdbus_example_objectmanager_xml,
+ output: [
+ 'objectmanager-rst-gen-org.gtk.GDBus.Example.ObjectManager.Animal.rst',
+ 'objectmanager-rst-gen-org.gtk.GDBus.Example.ObjectManager.Cat.rst',
+ ],
+ command: [
+ python,
+ gdbus_codegen,
+ '--interface-prefix', 'org.gtk.GDBus.Example.ObjectManager.',
+ '--generate-rst', 'objectmanager-rst-gen',
+ '--output-directory', '@OUTDIR@',
+ '@INPUT@',
+ ],
+)
+
libgdbus_example_objectmanager = library('gdbus-example-objectmanager',
gdbus_example_objectmanager_generated,
c_args : test_c_args,
@@ -25,6 +41,9 @@ libgdbus_example_objectmanager = library('gdbus-example-objectmanager',
install_dir : installed_tests_execdir)
libgdbus_example_objectmanager_dep = declare_dependency(
- sources : gdbus_example_objectmanager_generated[0],
+ sources : [
+ gdbus_example_objectmanager_generated[0],
+ gdbus_example_objectmanager_rst_gen[0],
+ ],
link_with : libgdbus_example_objectmanager,
dependencies : [libgio_dep])