mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-14 05:16:18 +01:00
66e4ba806a
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.
333 lines
10 KiB
Python
333 lines
10 KiB
Python
# 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))
|