Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/1571497/?format=api
{ "id": 1571497, "url": "http://patchwork.ozlabs.org/api/patches/1571497/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20211221065855.142578-21-marcandre.lureau@redhat.com/", "project": { "id": 14, "url": "http://patchwork.ozlabs.org/api/projects/14/?format=api", "name": "QEMU Development", "link_name": "qemu-devel", "list_id": "qemu-devel.nongnu.org", "list_email": "qemu-devel@nongnu.org", "web_url": "", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20211221065855.142578-21-marcandre.lureau@redhat.com>", "list_archive_url": null, "date": "2021-12-21T06:58:39", "name": "[PULL,v2,20/36] docs/sphinx: add sphinx modules to include D-Bus documentation", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "83599d488d7d0c30ee11fbd12cad23223466d4dd", "submitter": { "id": 66774, "url": "http://patchwork.ozlabs.org/api/people/66774/?format=api", "name": "Marc-André Lureau", "email": "marcandre.lureau@redhat.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20211221065855.142578-21-marcandre.lureau@redhat.com/mbox/", "series": [ { "id": 277865, "url": "http://patchwork.ozlabs.org/api/series/277865/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=277865", "date": "2021-12-21T06:58:19", "name": "[PULL,v2,01/36] ui/vdagent: add CHECK_SPICE_PROTOCOL_VERSION", "version": 2, "mbox": "http://patchwork.ozlabs.org/series/277865/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/1571497/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/1571497/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@bilbo.ozlabs.org", "Authentication-Results": [ "bilbo.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=b5eYx4F2;\n\tdkim-atps=neutral", "ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=<UNKNOWN>)", "relay.mimecast.com;\n auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=marcandre.lureau@redhat.com" ], "Received": [ "from lists.gnu.org (lists.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby bilbo.ozlabs.org (Postfix) with ESMTPS id 4JJ7pw2qwvz9s3q\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Dec 2021 18:47:52 +1100 (AEDT)", "from localhost ([::1]:37122 helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>)\n\tid 1mzZsE-00086H-5r\n\tfor incoming@patchwork.ozlabs.org; Tue, 21 Dec 2021 02:47:50 -0500", "from eggs.gnu.org ([209.51.188.92]:58746)\n by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <marcandre.lureau@redhat.com>)\n id 1mzZC3-0006o9-UC\n for qemu-devel@nongnu.org; Tue, 21 Dec 2021 02:04:16 -0500", "from us-smtp-delivery-124.mimecast.com ([170.10.133.124]:22590)\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <marcandre.lureau@redhat.com>)\n id 1mzZC0-00029A-F8\n for qemu-devel@nongnu.org; Tue, 21 Dec 2021 02:04:15 -0500", "from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com\n [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS\n (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id\n us-mta-664-ZS63TXCmNmCRouv_Wp1M3w-1; Tue, 21 Dec 2021 02:04:08 -0500", "from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com\n [10.5.11.16])\n (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n (No client certificate requested)\n by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 66D0A2F46;\n Tue, 21 Dec 2021 07:04:07 +0000 (UTC)", "from localhost (unknown [10.39.208.37])\n by smtp.corp.redhat.com (Postfix) with ESMTP id CAC75838E4;\n Tue, 21 Dec 2021 07:03:50 +0000 (UTC)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1640070251;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=aRILXhOIpdiTtyMy0stLTqiN3WJFSqWP8QCKwXfAz0E=;\n b=b5eYx4F2BI3740lmC/ESM/t75vmGo5l/U/EUX8HPnEOY2kH/kVeG2CL0tYihW0oqLopExO\n iyMUMRSm5Dy3qgETC3kw+/gdYO2eld8jF6MVerwVb9hhSB6oNA2Y0qam2/t+XVJNvXqMaL\n N+5+PxMCZwlt10T9Wn7UD+8BWhVrnyk=", "X-MC-Unique": "ZS63TXCmNmCRouv_Wp1M3w-1", "From": "marcandre.lureau@redhat.com", "To": "qemu-devel@nongnu.org", "Subject": "[PULL v2 20/36] docs/sphinx: add sphinx modules to include D-Bus\n documentation", "Date": "Tue, 21 Dec 2021 10:58:39 +0400", "Message-Id": "<20211221065855.142578-21-marcandre.lureau@redhat.com>", "In-Reply-To": "<20211221065855.142578-1-marcandre.lureau@redhat.com>", "References": "<20211221065855.142578-1-marcandre.lureau@redhat.com>", "MIME-Version": "1.0", "X-Scanned-By": "MIMEDefang 2.79 on 10.5.11.16", "X-Mimecast-Spam-Score": "0", "X-Mimecast-Originator": "redhat.com", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit", "Received-SPF": "pass client-ip=170.10.133.124;\n envelope-from=marcandre.lureau@redhat.com;\n helo=us-smtp-delivery-124.mimecast.com", "X-Spam_score_int": "-29", "X-Spam_score": "-3.0", "X-Spam_bar": "---", "X-Spam_report": "(-3.0 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.203,\n DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,\n RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001,\n SPF_HELO_NONE=0.001, SPF_PASS=-0.001,\n T_FILL_THIS_FORM_SHORT=0.01 autolearn=ham autolearn_force=no", "X-Spam_action": "no action", "X-BeenThere": "qemu-devel@nongnu.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<qemu-devel.nongnu.org>", "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>", "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>", "List-Post": "<mailto:qemu-devel@nongnu.org>", "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>", "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>", "Cc": "peter.maydell@linaro.org, richard.henderson@linaro.org, =?utf-8?q?Marc-A?=\n\t=?utf-8?q?ndr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>", "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org", "Sender": "\"Qemu-devel\"\n <qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>" }, "content": "From: Marc-André Lureau <marcandre.lureau@redhat.com>\n\nAdd a new dbus-doc directive to import D-Bus interfaces documentation\nfrom the introspection XML. The comments annotations follow the\ngtkdoc/kerneldoc style, and should be formatted with reST.\n\nNote: I realize after the fact that I was implementing those modules\nwith sphinx 4, and that we have much lower requirements. Instead of\nlowering the features and code (removing type annotations etc), let's\nhave a warning in the documentation when the D-Bus modules can't be\nused, and point to the source XML file in that case.\n\nSigned-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>\nAcked-by: Gerd Hoffmann <kraxel@redhat.com>\n---\n docs/conf.py | 8 +\n docs/sphinx/dbusdoc.py | 166 +++++++++++++++\n docs/sphinx/dbusdomain.py | 406 +++++++++++++++++++++++++++++++++++++\n docs/sphinx/dbusparser.py | 373 ++++++++++++++++++++++++++++++++++\n docs/sphinx/fakedbusdoc.py | 25 +++\n 5 files changed, 978 insertions(+)\n create mode 100644 docs/sphinx/dbusdoc.py\n create mode 100644 docs/sphinx/dbusdomain.py\n create mode 100644 docs/sphinx/dbusparser.py\n create mode 100644 docs/sphinx/fakedbusdoc.py", "diff": "diff --git a/docs/conf.py b/docs/conf.py\nindex 763e7d243448..e79015975e6a 100644\n--- a/docs/conf.py\n+++ b/docs/conf.py\n@@ -73,6 +73,12 @@\n # ones.\n extensions = ['kerneldoc', 'qmp_lexer', 'hxtool', 'depfile', 'qapidoc']\n \n+if sphinx.version_info[:3] > (4, 0, 0):\n+ tags.add('sphinx4')\n+ extensions += ['dbusdoc']\n+else:\n+ extensions += ['fakedbusdoc']\n+\n # Add any paths that contain templates here, relative to this directory.\n templates_path = [os.path.join(qemu_docdir, '_templates')]\n \n@@ -311,3 +317,5 @@\n kerneldoc_srctree = os.path.join(qemu_docdir, '..')\n hxtool_srctree = os.path.join(qemu_docdir, '..')\n qapidoc_srctree = os.path.join(qemu_docdir, '..')\n+dbusdoc_srctree = os.path.join(qemu_docdir, '..')\n+dbus_index_common_prefix = [\"org.qemu.\"]\ndiff --git a/docs/sphinx/dbusdoc.py b/docs/sphinx/dbusdoc.py\nnew file mode 100644\nindex 000000000000..be284ed08fd7\n--- /dev/null\n+++ b/docs/sphinx/dbusdoc.py\n@@ -0,0 +1,166 @@\n+# D-Bus XML documentation extension\n+#\n+# Copyright (C) 2021, Red Hat Inc.\n+#\n+# SPDX-License-Identifier: LGPL-2.1-or-later\n+#\n+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>\n+\"\"\"dbus-doc is a Sphinx extension that provides documentation from D-Bus XML.\"\"\"\n+\n+import os\n+import re\n+from typing import (\n+ TYPE_CHECKING,\n+ Any,\n+ Callable,\n+ Dict,\n+ Iterator,\n+ List,\n+ Optional,\n+ Sequence,\n+ Set,\n+ Tuple,\n+ Type,\n+ TypeVar,\n+ Union,\n+)\n+\n+import sphinx\n+from docutils import nodes\n+from docutils.nodes import Element, Node\n+from docutils.parsers.rst import Directive, directives\n+from docutils.parsers.rst.states import RSTState\n+from docutils.statemachine import StringList, ViewList\n+from sphinx.application import Sphinx\n+from sphinx.errors import ExtensionError\n+from sphinx.util import logging\n+from sphinx.util.docstrings import prepare_docstring\n+from sphinx.util.docutils import SphinxDirective, switch_source_input\n+from sphinx.util.nodes import nested_parse_with_titles\n+\n+import dbusdomain\n+from dbusparser import parse_dbus_xml\n+\n+logger = logging.getLogger(__name__)\n+\n+__version__ = \"1.0\"\n+\n+\n+class DBusDoc:\n+ def __init__(self, sphinx_directive, dbusfile):\n+ self._cur_doc = None\n+ self._sphinx_directive = sphinx_directive\n+ self._dbusfile = dbusfile\n+ self._top_node = nodes.section()\n+ self.result = StringList()\n+ self.indent = \"\"\n+\n+ def add_line(self, line: str, *lineno: int) -> None:\n+ \"\"\"Append one line of generated reST to the output.\"\"\"\n+ if line.strip(): # not a blank line\n+ self.result.append(self.indent + line, self._dbusfile, *lineno)\n+ else:\n+ self.result.append(\"\", self._dbusfile, *lineno)\n+\n+ def add_method(self, method):\n+ self.add_line(f\".. dbus:method:: {method.name}\")\n+ self.add_line(\"\")\n+ self.indent += \" \"\n+ for arg in method.in_args:\n+ self.add_line(f\":arg {arg.signature} {arg.name}: {arg.doc_string}\")\n+ for arg in method.out_args:\n+ self.add_line(f\":ret {arg.signature} {arg.name}: {arg.doc_string}\")\n+ self.add_line(\"\")\n+ for line in prepare_docstring(\"\\n\" + method.doc_string):\n+ self.add_line(line)\n+ self.indent = self.indent[:-3]\n+\n+ def add_signal(self, signal):\n+ self.add_line(f\".. dbus:signal:: {signal.name}\")\n+ self.add_line(\"\")\n+ self.indent += \" \"\n+ for arg in signal.args:\n+ self.add_line(f\":arg {arg.signature} {arg.name}: {arg.doc_string}\")\n+ self.add_line(\"\")\n+ for line in prepare_docstring(\"\\n\" + signal.doc_string):\n+ self.add_line(line)\n+ self.indent = self.indent[:-3]\n+\n+ def add_property(self, prop):\n+ self.add_line(f\".. dbus:property:: {prop.name}\")\n+ self.indent += \" \"\n+ self.add_line(f\":type: {prop.signature}\")\n+ access = {\"read\": \"readonly\", \"write\": \"writeonly\", \"readwrite\": \"readwrite\"}[\n+ prop.access\n+ ]\n+ self.add_line(f\":{access}:\")\n+ if prop.emits_changed_signal:\n+ self.add_line(f\":emits-changed: yes\")\n+ self.add_line(\"\")\n+ for line in prepare_docstring(\"\\n\" + prop.doc_string):\n+ self.add_line(line)\n+ self.indent = self.indent[:-3]\n+\n+ def add_interface(self, iface):\n+ self.add_line(f\".. dbus:interface:: {iface.name}\")\n+ self.add_line(\"\")\n+ self.indent += \" \"\n+ for line in prepare_docstring(\"\\n\" + iface.doc_string):\n+ self.add_line(line)\n+ for method in iface.methods:\n+ self.add_method(method)\n+ for sig in iface.signals:\n+ self.add_signal(sig)\n+ for prop in iface.properties:\n+ self.add_property(prop)\n+ self.indent = self.indent[:-3]\n+\n+\n+def parse_generated_content(state: RSTState, content: StringList) -> List[Node]:\n+ \"\"\"Parse a generated content by Documenter.\"\"\"\n+ with switch_source_input(state, content):\n+ node = nodes.paragraph()\n+ node.document = state.document\n+ state.nested_parse(content, 0, node)\n+\n+ return node.children\n+\n+\n+class DBusDocDirective(SphinxDirective):\n+ \"\"\"Extract documentation from the specified D-Bus XML file\"\"\"\n+\n+ has_content = True\n+ required_arguments = 1\n+ optional_arguments = 0\n+ final_argument_whitespace = True\n+\n+ def run(self):\n+ reporter = self.state.document.reporter\n+\n+ try:\n+ source, lineno = reporter.get_source_and_line(self.lineno) # type: ignore\n+ except AttributeError:\n+ source, lineno = (None, None)\n+\n+ logger.debug(\"[dbusdoc] %s:%s: input:\\n%s\", source, lineno, self.block_text)\n+\n+ env = self.state.document.settings.env\n+ dbusfile = env.config.qapidoc_srctree + \"/\" + self.arguments[0]\n+ with open(dbusfile, \"rb\") as f:\n+ xml_data = f.read()\n+ xml = parse_dbus_xml(xml_data)\n+ doc = DBusDoc(self, dbusfile)\n+ for iface in xml:\n+ doc.add_interface(iface)\n+\n+ result = parse_generated_content(self.state, doc.result)\n+ return result\n+\n+\n+def setup(app: Sphinx) -> Dict[str, Any]:\n+ \"\"\"Register dbus-doc directive with Sphinx\"\"\"\n+ app.add_config_value(\"dbusdoc_srctree\", None, \"env\")\n+ app.add_directive(\"dbus-doc\", DBusDocDirective)\n+ dbusdomain.setup(app)\n+\n+ return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True)\ndiff --git a/docs/sphinx/dbusdomain.py b/docs/sphinx/dbusdomain.py\nnew file mode 100644\nindex 000000000000..2ea95af623d2\n--- /dev/null\n+++ b/docs/sphinx/dbusdomain.py\n@@ -0,0 +1,406 @@\n+# D-Bus sphinx domain extension\n+#\n+# Copyright (C) 2021, Red Hat Inc.\n+#\n+# SPDX-License-Identifier: LGPL-2.1-or-later\n+#\n+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>\n+\n+from typing import (\n+ Any,\n+ Dict,\n+ Iterable,\n+ Iterator,\n+ List,\n+ NamedTuple,\n+ Optional,\n+ Tuple,\n+ cast,\n+)\n+\n+from docutils import nodes\n+from docutils.nodes import Element, Node\n+from docutils.parsers.rst import directives\n+from sphinx import addnodes\n+from sphinx.addnodes import desc_signature, pending_xref\n+from sphinx.directives import ObjectDescription\n+from sphinx.domains import Domain, Index, IndexEntry, ObjType\n+from sphinx.locale import _\n+from sphinx.roles import XRefRole\n+from sphinx.util import nodes as node_utils\n+from sphinx.util.docfields import Field, TypedField\n+from sphinx.util.typing import OptionSpec\n+\n+\n+class DBusDescription(ObjectDescription[str]):\n+ \"\"\"Base class for DBus objects\"\"\"\n+\n+ option_spec: OptionSpec = ObjectDescription.option_spec.copy()\n+ option_spec.update(\n+ {\n+ \"deprecated\": directives.flag,\n+ }\n+ )\n+\n+ def get_index_text(self, modname: str, name: str) -> str:\n+ \"\"\"Return the text for the index entry of the object.\"\"\"\n+ raise NotImplementedError(\"must be implemented in subclasses\")\n+\n+ def add_target_and_index(\n+ self, name: str, sig: str, signode: desc_signature\n+ ) -> None:\n+ ifacename = self.env.ref_context.get(\"dbus:interface\")\n+ node_id = name\n+ if ifacename:\n+ node_id = f\"{ifacename}.{node_id}\"\n+\n+ signode[\"names\"].append(name)\n+ signode[\"ids\"].append(node_id)\n+\n+ if \"noindexentry\" not in self.options:\n+ indextext = self.get_index_text(ifacename, name)\n+ if indextext:\n+ self.indexnode[\"entries\"].append(\n+ (\"single\", indextext, node_id, \"\", None)\n+ )\n+\n+ domain = cast(DBusDomain, self.env.get_domain(\"dbus\"))\n+ domain.note_object(name, self.objtype, node_id, location=signode)\n+\n+\n+class DBusInterface(DBusDescription):\n+ \"\"\"\n+ Implementation of ``dbus:interface``.\n+ \"\"\"\n+\n+ def get_index_text(self, ifacename: str, name: str) -> str:\n+ return ifacename\n+\n+ def before_content(self) -> None:\n+ self.env.ref_context[\"dbus:interface\"] = self.arguments[0]\n+\n+ def after_content(self) -> None:\n+ self.env.ref_context.pop(\"dbus:interface\")\n+\n+ def handle_signature(self, sig: str, signode: desc_signature) -> str:\n+ signode += addnodes.desc_annotation(\"interface \", \"interface \")\n+ signode += addnodes.desc_name(sig, sig)\n+ return sig\n+\n+ def run(self) -> List[Node]:\n+ _, node = super().run()\n+ name = self.arguments[0]\n+ section = nodes.section(ids=[name + \"-section\"])\n+ section += nodes.title(name, \"%s interface\" % name)\n+ section += node\n+ return [self.indexnode, section]\n+\n+\n+class DBusMember(DBusDescription):\n+\n+ signal = False\n+\n+\n+class DBusMethod(DBusMember):\n+ \"\"\"\n+ Implementation of ``dbus:method``.\n+ \"\"\"\n+\n+ option_spec: OptionSpec = DBusMember.option_spec.copy()\n+ option_spec.update(\n+ {\n+ \"noreply\": directives.flag,\n+ }\n+ )\n+\n+ doc_field_types: List[Field] = [\n+ TypedField(\n+ \"arg\",\n+ label=_(\"Arguments\"),\n+ names=(\"arg\",),\n+ rolename=\"arg\",\n+ typerolename=None,\n+ typenames=(\"argtype\", \"type\"),\n+ ),\n+ TypedField(\n+ \"ret\",\n+ label=_(\"Returns\"),\n+ names=(\"ret\",),\n+ rolename=\"ret\",\n+ typerolename=None,\n+ typenames=(\"rettype\", \"type\"),\n+ ),\n+ ]\n+\n+ def get_index_text(self, ifacename: str, name: str) -> str:\n+ return _(\"%s() (%s method)\") % (name, ifacename)\n+\n+ def handle_signature(self, sig: str, signode: desc_signature) -> str:\n+ params = addnodes.desc_parameterlist()\n+ returns = addnodes.desc_parameterlist()\n+\n+ contentnode = addnodes.desc_content()\n+ self.state.nested_parse(self.content, self.content_offset, contentnode)\n+ for child in contentnode:\n+ if isinstance(child, nodes.field_list):\n+ for field in child:\n+ ty, sg, name = field[0].astext().split(None, 2)\n+ param = addnodes.desc_parameter()\n+ param += addnodes.desc_sig_keyword_type(sg, sg)\n+ param += addnodes.desc_sig_space()\n+ param += addnodes.desc_sig_name(name, name)\n+ if ty == \"arg\":\n+ params += param\n+ elif ty == \"ret\":\n+ returns += param\n+\n+ anno = \"signal \" if self.signal else \"method \"\n+ signode += addnodes.desc_annotation(anno, anno)\n+ signode += addnodes.desc_name(sig, sig)\n+ signode += params\n+ if not self.signal and \"noreply\" not in self.options:\n+ ret = addnodes.desc_returns()\n+ ret += returns\n+ signode += ret\n+\n+ return sig\n+\n+\n+class DBusSignal(DBusMethod):\n+ \"\"\"\n+ Implementation of ``dbus:signal``.\n+ \"\"\"\n+\n+ doc_field_types: List[Field] = [\n+ TypedField(\n+ \"arg\",\n+ label=_(\"Arguments\"),\n+ names=(\"arg\",),\n+ rolename=\"arg\",\n+ typerolename=None,\n+ typenames=(\"argtype\", \"type\"),\n+ ),\n+ ]\n+ signal = True\n+\n+ def get_index_text(self, ifacename: str, name: str) -> str:\n+ return _(\"%s() (%s signal)\") % (name, ifacename)\n+\n+\n+class DBusProperty(DBusMember):\n+ \"\"\"\n+ Implementation of ``dbus:property``.\n+ \"\"\"\n+\n+ option_spec: OptionSpec = DBusMember.option_spec.copy()\n+ option_spec.update(\n+ {\n+ \"type\": directives.unchanged,\n+ \"readonly\": directives.flag,\n+ \"writeonly\": directives.flag,\n+ \"readwrite\": directives.flag,\n+ \"emits-changed\": directives.unchanged,\n+ }\n+ )\n+\n+ doc_field_types: List[Field] = []\n+\n+ def get_index_text(self, ifacename: str, name: str) -> str:\n+ return _(\"%s (%s property)\") % (name, ifacename)\n+\n+ def transform_content(self, contentnode: addnodes.desc_content) -> None:\n+ fieldlist = nodes.field_list()\n+ access = None\n+ if \"readonly\" in self.options:\n+ access = _(\"read-only\")\n+ if \"writeonly\" in self.options:\n+ access = _(\"write-only\")\n+ if \"readwrite\" in self.options:\n+ access = _(\"read & write\")\n+ if access:\n+ content = nodes.Text(access)\n+ fieldname = nodes.field_name(\"\", _(\"Access\"))\n+ fieldbody = nodes.field_body(\"\", nodes.paragraph(\"\", \"\", content))\n+ field = nodes.field(\"\", fieldname, fieldbody)\n+ fieldlist += field\n+ emits = self.options.get(\"emits-changed\", None)\n+ if emits:\n+ content = nodes.Text(emits)\n+ fieldname = nodes.field_name(\"\", _(\"Emits Changed\"))\n+ fieldbody = nodes.field_body(\"\", nodes.paragraph(\"\", \"\", content))\n+ field = nodes.field(\"\", fieldname, fieldbody)\n+ fieldlist += field\n+ if len(fieldlist) > 0:\n+ contentnode.insert(0, fieldlist)\n+\n+ def handle_signature(self, sig: str, signode: desc_signature) -> str:\n+ contentnode = addnodes.desc_content()\n+ self.state.nested_parse(self.content, self.content_offset, contentnode)\n+ ty = self.options.get(\"type\")\n+\n+ signode += addnodes.desc_annotation(\"property \", \"property \")\n+ signode += addnodes.desc_name(sig, sig)\n+ signode += addnodes.desc_sig_punctuation(\"\", \":\")\n+ signode += addnodes.desc_sig_keyword_type(ty, ty)\n+ return sig\n+\n+ def run(self) -> List[Node]:\n+ self.name = \"dbus:member\"\n+ return super().run()\n+\n+\n+class DBusXRef(XRefRole):\n+ def process_link(self, env, refnode, has_explicit_title, title, target):\n+ refnode[\"dbus:interface\"] = env.ref_context.get(\"dbus:interface\")\n+ if not has_explicit_title:\n+ title = title.lstrip(\".\") # only has a meaning for the target\n+ target = target.lstrip(\"~\") # only has a meaning for the title\n+ # if the first character is a tilde, don't display the module/class\n+ # parts of the contents\n+ if title[0:1] == \"~\":\n+ title = title[1:]\n+ dot = title.rfind(\".\")\n+ if dot != -1:\n+ title = title[dot + 1 :]\n+ # if the first character is a dot, search more specific namespaces first\n+ # else search builtins first\n+ if target[0:1] == \".\":\n+ target = target[1:]\n+ refnode[\"refspecific\"] = True\n+ return title, target\n+\n+\n+class DBusIndex(Index):\n+ \"\"\"\n+ Index subclass to provide a D-Bus interfaces index.\n+ \"\"\"\n+\n+ name = \"dbusindex\"\n+ localname = _(\"D-Bus Interfaces Index\")\n+ shortname = _(\"dbus\")\n+\n+ def generate(\n+ self, docnames: Iterable[str] = None\n+ ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:\n+ content: Dict[str, List[IndexEntry]] = {}\n+ # list of prefixes to ignore\n+ ignores: List[str] = self.domain.env.config[\"dbus_index_common_prefix\"]\n+ ignores = sorted(ignores, key=len, reverse=True)\n+\n+ ifaces = sorted(\n+ [\n+ x\n+ for x in self.domain.data[\"objects\"].items()\n+ if x[1].objtype == \"interface\"\n+ ],\n+ key=lambda x: x[0].lower(),\n+ )\n+ for name, (docname, node_id, _) in ifaces:\n+ if docnames and docname not in docnames:\n+ continue\n+\n+ for ignore in ignores:\n+ if name.startswith(ignore):\n+ name = name[len(ignore) :]\n+ stripped = ignore\n+ break\n+ else:\n+ stripped = \"\"\n+\n+ entries = content.setdefault(name[0].lower(), [])\n+ entries.append(IndexEntry(stripped + name, 0, docname, node_id, \"\", \"\", \"\"))\n+\n+ # sort by first letter\n+ sorted_content = sorted(content.items())\n+\n+ return sorted_content, False\n+\n+\n+class ObjectEntry(NamedTuple):\n+ docname: str\n+ node_id: str\n+ objtype: str\n+\n+\n+class DBusDomain(Domain):\n+ \"\"\"\n+ Implementation of the D-Bus domain.\n+ \"\"\"\n+\n+ name = \"dbus\"\n+ label = \"D-Bus\"\n+ object_types: Dict[str, ObjType] = {\n+ \"interface\": ObjType(_(\"interface\"), \"iface\", \"obj\"),\n+ \"method\": ObjType(_(\"method\"), \"meth\", \"obj\"),\n+ \"signal\": ObjType(_(\"signal\"), \"sig\", \"obj\"),\n+ \"property\": ObjType(_(\"property\"), \"attr\", \"_prop\", \"obj\"),\n+ }\n+ directives = {\n+ \"interface\": DBusInterface,\n+ \"method\": DBusMethod,\n+ \"signal\": DBusSignal,\n+ \"property\": DBusProperty,\n+ }\n+ roles = {\n+ \"iface\": DBusXRef(),\n+ \"meth\": DBusXRef(),\n+ \"sig\": DBusXRef(),\n+ \"prop\": DBusXRef(),\n+ }\n+ initial_data: Dict[str, Dict[str, Tuple[Any]]] = {\n+ \"objects\": {}, # fullname -> ObjectEntry\n+ }\n+ indices = [\n+ DBusIndex,\n+ ]\n+\n+ @property\n+ def objects(self) -> Dict[str, ObjectEntry]:\n+ return self.data.setdefault(\"objects\", {}) # fullname -> ObjectEntry\n+\n+ def note_object(\n+ self, name: str, objtype: str, node_id: str, location: Any = None\n+ ) -> None:\n+ self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype)\n+\n+ def clear_doc(self, docname: str) -> None:\n+ for fullname, obj in list(self.objects.items()):\n+ if obj.docname == docname:\n+ del self.objects[fullname]\n+\n+ def find_obj(self, typ: str, name: str) -> Optional[Tuple[str, ObjectEntry]]:\n+ # skip parens\n+ if name[-2:] == \"()\":\n+ name = name[:-2]\n+ if typ in (\"meth\", \"sig\", \"prop\"):\n+ try:\n+ ifacename, name = name.rsplit(\".\", 1)\n+ except ValueError:\n+ pass\n+ return self.objects.get(name)\n+\n+ def resolve_xref(\n+ self,\n+ env: \"BuildEnvironment\",\n+ fromdocname: str,\n+ builder: \"Builder\",\n+ typ: str,\n+ target: str,\n+ node: pending_xref,\n+ contnode: Element,\n+ ) -> Optional[Element]:\n+ \"\"\"Resolve the pending_xref *node* with the given *typ* and *target*.\"\"\"\n+ objdef = self.find_obj(typ, target)\n+ if objdef:\n+ return node_utils.make_refnode(\n+ builder, fromdocname, objdef.docname, objdef.node_id, contnode\n+ )\n+\n+ def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:\n+ for refname, obj in self.objects.items():\n+ yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)\n+\n+\n+def setup(app):\n+ app.add_domain(DBusDomain)\n+ app.add_config_value(\"dbus_index_common_prefix\", [], \"env\")\ndiff --git a/docs/sphinx/dbusparser.py b/docs/sphinx/dbusparser.py\nnew file mode 100644\nindex 000000000000..024553eae7b5\n--- /dev/null\n+++ b/docs/sphinx/dbusparser.py\n@@ -0,0 +1,373 @@\n+# Based from \"GDBus - GLib D-Bus Library\":\n+#\n+# Copyright (C) 2008-2011 Red Hat, Inc.\n+#\n+# This library is free software; you can redistribute it and/or\n+# modify it under the terms of the GNU Lesser General Public\n+# License as published by the Free Software Foundation; either\n+# version 2.1 of the License, or (at your option) any later version.\n+#\n+# This library is distributed in the hope that it will be useful,\n+# but WITHOUT ANY WARRANTY; without even the implied warranty of\n+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n+# Lesser General Public License for more details.\n+#\n+# You should have received a copy of the GNU Lesser General\n+# Public License along with this library; if not, see <http://www.gnu.org/licenses/>.\n+#\n+# Author: David Zeuthen <davidz@redhat.com>\n+\n+import xml.parsers.expat\n+\n+\n+class Annotation:\n+ def __init__(self, key, value):\n+ self.key = key\n+ self.value = value\n+ self.annotations = []\n+ self.since = \"\"\n+\n+\n+class Arg:\n+ def __init__(self, name, signature):\n+ self.name = name\n+ self.signature = signature\n+ self.annotations = []\n+ self.doc_string = \"\"\n+ self.since = \"\"\n+\n+\n+class Method:\n+ def __init__(self, name, h_type_implies_unix_fd=True):\n+ self.name = name\n+ self.h_type_implies_unix_fd = h_type_implies_unix_fd\n+ self.in_args = []\n+ self.out_args = []\n+ self.annotations = []\n+ self.doc_string = \"\"\n+ self.since = \"\"\n+ self.deprecated = False\n+ self.unix_fd = False\n+\n+\n+class Signal:\n+ def __init__(self, name):\n+ self.name = name\n+ self.args = []\n+ self.annotations = []\n+ self.doc_string = \"\"\n+ self.since = \"\"\n+ self.deprecated = False\n+\n+\n+class Property:\n+ def __init__(self, name, signature, access):\n+ self.name = name\n+ self.signature = signature\n+ self.access = access\n+ self.annotations = []\n+ self.arg = Arg(\"value\", self.signature)\n+ self.arg.annotations = self.annotations\n+ self.readable = False\n+ self.writable = False\n+ if self.access == \"readwrite\":\n+ self.readable = True\n+ self.writable = True\n+ elif self.access == \"read\":\n+ self.readable = True\n+ elif self.access == \"write\":\n+ self.writable = True\n+ else:\n+ raise ValueError('Invalid access type \"{}\"'.format(self.access))\n+ self.doc_string = \"\"\n+ self.since = \"\"\n+ self.deprecated = False\n+ self.emits_changed_signal = True\n+\n+\n+class Interface:\n+ def __init__(self, name):\n+ self.name = name\n+ self.methods = []\n+ self.signals = []\n+ self.properties = []\n+ self.annotations = []\n+ self.doc_string = \"\"\n+ self.doc_string_brief = \"\"\n+ self.since = \"\"\n+ self.deprecated = False\n+\n+\n+class DBusXMLParser:\n+ STATE_TOP = \"top\"\n+ STATE_NODE = \"node\"\n+ STATE_INTERFACE = \"interface\"\n+ STATE_METHOD = \"method\"\n+ STATE_SIGNAL = \"signal\"\n+ STATE_PROPERTY = \"property\"\n+ STATE_ARG = \"arg\"\n+ STATE_ANNOTATION = \"annotation\"\n+ STATE_IGNORED = \"ignored\"\n+\n+ def __init__(self, xml_data, h_type_implies_unix_fd=True):\n+ self._parser = xml.parsers.expat.ParserCreate()\n+ self._parser.CommentHandler = self.handle_comment\n+ self._parser.CharacterDataHandler = self.handle_char_data\n+ self._parser.StartElementHandler = self.handle_start_element\n+ self._parser.EndElementHandler = self.handle_end_element\n+\n+ self.parsed_interfaces = []\n+ self._cur_object = None\n+\n+ self.state = DBusXMLParser.STATE_TOP\n+ self.state_stack = []\n+ self._cur_object = None\n+ self._cur_object_stack = []\n+\n+ self.doc_comment_last_symbol = \"\"\n+\n+ self._h_type_implies_unix_fd = h_type_implies_unix_fd\n+\n+ self._parser.Parse(xml_data)\n+\n+ COMMENT_STATE_BEGIN = \"begin\"\n+ COMMENT_STATE_PARAMS = \"params\"\n+ COMMENT_STATE_BODY = \"body\"\n+ COMMENT_STATE_SKIP = \"skip\"\n+\n+ def handle_comment(self, data):\n+ comment_state = DBusXMLParser.COMMENT_STATE_BEGIN\n+ lines = data.split(\"\\n\")\n+ symbol = \"\"\n+ body = \"\"\n+ in_para = False\n+ params = {}\n+ for line in lines:\n+ orig_line = line\n+ line = line.lstrip()\n+ if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN:\n+ if len(line) > 0:\n+ colon_index = line.find(\": \")\n+ if colon_index == -1:\n+ if line.endswith(\":\"):\n+ symbol = line[0 : len(line) - 1]\n+ comment_state = DBusXMLParser.COMMENT_STATE_PARAMS\n+ else:\n+ comment_state = DBusXMLParser.COMMENT_STATE_SKIP\n+ else:\n+ symbol = line[0:colon_index]\n+ rest_of_line = line[colon_index + 2 :].strip()\n+ if len(rest_of_line) > 0:\n+ body += rest_of_line + \"\\n\"\n+ comment_state = DBusXMLParser.COMMENT_STATE_PARAMS\n+ elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS:\n+ if line.startswith(\"@\"):\n+ colon_index = line.find(\": \")\n+ if colon_index == -1:\n+ comment_state = DBusXMLParser.COMMENT_STATE_BODY\n+ if not in_para:\n+ in_para = True\n+ body += orig_line + \"\\n\"\n+ else:\n+ param = line[1:colon_index]\n+ docs = line[colon_index + 2 :]\n+ params[param] = docs\n+ else:\n+ comment_state = DBusXMLParser.COMMENT_STATE_BODY\n+ if len(line) > 0:\n+ if not in_para:\n+ in_para = True\n+ body += orig_line + \"\\n\"\n+ elif comment_state == DBusXMLParser.COMMENT_STATE_BODY:\n+ if len(line) > 0:\n+ if not in_para:\n+ in_para = True\n+ body += orig_line + \"\\n\"\n+ else:\n+ if in_para:\n+ body += \"\\n\"\n+ in_para = False\n+ if in_para:\n+ body += \"\\n\"\n+\n+ if symbol != \"\":\n+ self.doc_comment_last_symbol = symbol\n+ self.doc_comment_params = params\n+ self.doc_comment_body = body\n+\n+ def handle_char_data(self, data):\n+ # print 'char_data=%s'%data\n+ pass\n+\n+ def handle_start_element(self, name, attrs):\n+ old_state = self.state\n+ old_cur_object = self._cur_object\n+ if self.state == DBusXMLParser.STATE_IGNORED:\n+ self.state = DBusXMLParser.STATE_IGNORED\n+ elif self.state == DBusXMLParser.STATE_TOP:\n+ if name == DBusXMLParser.STATE_NODE:\n+ self.state = DBusXMLParser.STATE_NODE\n+ else:\n+ self.state = DBusXMLParser.STATE_IGNORED\n+ elif self.state == DBusXMLParser.STATE_NODE:\n+ if name == DBusXMLParser.STATE_INTERFACE:\n+ self.state = DBusXMLParser.STATE_INTERFACE\n+ iface = Interface(attrs[\"name\"])\n+ self._cur_object = iface\n+ self.parsed_interfaces.append(iface)\n+ elif name == DBusXMLParser.STATE_ANNOTATION:\n+ self.state = DBusXMLParser.STATE_ANNOTATION\n+ anno = Annotation(attrs[\"name\"], attrs[\"value\"])\n+ self._cur_object.annotations.append(anno)\n+ self._cur_object = anno\n+ else:\n+ self.state = DBusXMLParser.STATE_IGNORED\n+\n+ # assign docs, if any\n+ if \"name\" in attrs and self.doc_comment_last_symbol == attrs[\"name\"]:\n+ self._cur_object.doc_string = self.doc_comment_body\n+ if \"short_description\" in self.doc_comment_params:\n+ short_description = self.doc_comment_params[\"short_description\"]\n+ self._cur_object.doc_string_brief = short_description\n+ if \"since\" in self.doc_comment_params:\n+ self._cur_object.since = self.doc_comment_params[\"since\"].strip()\n+\n+ elif self.state == DBusXMLParser.STATE_INTERFACE:\n+ if name == DBusXMLParser.STATE_METHOD:\n+ self.state = DBusXMLParser.STATE_METHOD\n+ method = Method(\n+ attrs[\"name\"], h_type_implies_unix_fd=self._h_type_implies_unix_fd\n+ )\n+ self._cur_object.methods.append(method)\n+ self._cur_object = method\n+ elif name == DBusXMLParser.STATE_SIGNAL:\n+ self.state = DBusXMLParser.STATE_SIGNAL\n+ signal = Signal(attrs[\"name\"])\n+ self._cur_object.signals.append(signal)\n+ self._cur_object = signal\n+ elif name == DBusXMLParser.STATE_PROPERTY:\n+ self.state = DBusXMLParser.STATE_PROPERTY\n+ prop = Property(attrs[\"name\"], attrs[\"type\"], attrs[\"access\"])\n+ self._cur_object.properties.append(prop)\n+ self._cur_object = prop\n+ elif name == DBusXMLParser.STATE_ANNOTATION:\n+ self.state = DBusXMLParser.STATE_ANNOTATION\n+ anno = Annotation(attrs[\"name\"], attrs[\"value\"])\n+ self._cur_object.annotations.append(anno)\n+ self._cur_object = anno\n+ else:\n+ self.state = DBusXMLParser.STATE_IGNORED\n+\n+ # assign docs, if any\n+ if \"name\" in attrs and self.doc_comment_last_symbol == attrs[\"name\"]:\n+ self._cur_object.doc_string = self.doc_comment_body\n+ if \"since\" in self.doc_comment_params:\n+ self._cur_object.since = self.doc_comment_params[\"since\"].strip()\n+\n+ elif self.state == DBusXMLParser.STATE_METHOD:\n+ if name == DBusXMLParser.STATE_ARG:\n+ self.state = DBusXMLParser.STATE_ARG\n+ arg_name = None\n+ if \"name\" in attrs:\n+ arg_name = attrs[\"name\"]\n+ arg = Arg(arg_name, attrs[\"type\"])\n+ direction = attrs.get(\"direction\", \"in\")\n+ if direction == \"in\":\n+ self._cur_object.in_args.append(arg)\n+ elif direction == \"out\":\n+ self._cur_object.out_args.append(arg)\n+ else:\n+ raise ValueError('Invalid direction \"{}\"'.format(direction))\n+ self._cur_object = arg\n+ elif name == DBusXMLParser.STATE_ANNOTATION:\n+ self.state = DBusXMLParser.STATE_ANNOTATION\n+ anno = Annotation(attrs[\"name\"], attrs[\"value\"])\n+ self._cur_object.annotations.append(anno)\n+ self._cur_object = anno\n+ else:\n+ self.state = DBusXMLParser.STATE_IGNORED\n+\n+ # assign docs, if any\n+ if self.doc_comment_last_symbol == old_cur_object.name:\n+ if \"name\" in attrs and attrs[\"name\"] in self.doc_comment_params:\n+ doc_string = self.doc_comment_params[attrs[\"name\"]]\n+ if doc_string is not None:\n+ self._cur_object.doc_string = doc_string\n+ if \"since\" in self.doc_comment_params:\n+ self._cur_object.since = self.doc_comment_params[\n+ \"since\"\n+ ].strip()\n+\n+ elif self.state == DBusXMLParser.STATE_SIGNAL:\n+ if name == DBusXMLParser.STATE_ARG:\n+ self.state = DBusXMLParser.STATE_ARG\n+ arg_name = None\n+ if \"name\" in attrs:\n+ arg_name = attrs[\"name\"]\n+ arg = Arg(arg_name, attrs[\"type\"])\n+ self._cur_object.args.append(arg)\n+ self._cur_object = arg\n+ elif name == DBusXMLParser.STATE_ANNOTATION:\n+ self.state = DBusXMLParser.STATE_ANNOTATION\n+ anno = Annotation(attrs[\"name\"], attrs[\"value\"])\n+ self._cur_object.annotations.append(anno)\n+ self._cur_object = anno\n+ else:\n+ self.state = DBusXMLParser.STATE_IGNORED\n+\n+ # assign docs, if any\n+ if self.doc_comment_last_symbol == old_cur_object.name:\n+ if \"name\" in attrs and attrs[\"name\"] in self.doc_comment_params:\n+ doc_string = self.doc_comment_params[attrs[\"name\"]]\n+ if doc_string is not None:\n+ self._cur_object.doc_string = doc_string\n+ if \"since\" in self.doc_comment_params:\n+ self._cur_object.since = self.doc_comment_params[\n+ \"since\"\n+ ].strip()\n+\n+ elif self.state == DBusXMLParser.STATE_PROPERTY:\n+ if name == DBusXMLParser.STATE_ANNOTATION:\n+ self.state = DBusXMLParser.STATE_ANNOTATION\n+ anno = Annotation(attrs[\"name\"], attrs[\"value\"])\n+ self._cur_object.annotations.append(anno)\n+ self._cur_object = anno\n+ else:\n+ self.state = DBusXMLParser.STATE_IGNORED\n+\n+ elif self.state == DBusXMLParser.STATE_ARG:\n+ if name == DBusXMLParser.STATE_ANNOTATION:\n+ self.state = DBusXMLParser.STATE_ANNOTATION\n+ anno = Annotation(attrs[\"name\"], attrs[\"value\"])\n+ self._cur_object.annotations.append(anno)\n+ self._cur_object = anno\n+ else:\n+ self.state = DBusXMLParser.STATE_IGNORED\n+\n+ elif self.state == DBusXMLParser.STATE_ANNOTATION:\n+ if name == DBusXMLParser.STATE_ANNOTATION:\n+ self.state = DBusXMLParser.STATE_ANNOTATION\n+ anno = Annotation(attrs[\"name\"], attrs[\"value\"])\n+ self._cur_object.annotations.append(anno)\n+ self._cur_object = anno\n+ else:\n+ self.state = DBusXMLParser.STATE_IGNORED\n+\n+ else:\n+ raise ValueError(\n+ 'Unhandled state \"{}\" while entering element with name \"{}\"'.format(\n+ self.state, name\n+ )\n+ )\n+\n+ self.state_stack.append(old_state)\n+ self._cur_object_stack.append(old_cur_object)\n+\n+ def handle_end_element(self, name):\n+ self.state = self.state_stack.pop()\n+ self._cur_object = self._cur_object_stack.pop()\n+\n+\n+def parse_dbus_xml(xml_data):\n+ parser = DBusXMLParser(xml_data, True)\n+ return parser.parsed_interfaces\ndiff --git a/docs/sphinx/fakedbusdoc.py b/docs/sphinx/fakedbusdoc.py\nnew file mode 100644\nindex 000000000000..a680b257547f\n--- /dev/null\n+++ b/docs/sphinx/fakedbusdoc.py\n@@ -0,0 +1,25 @@\n+# D-Bus XML documentation extension, compatibility gunk for <sphinx4\n+#\n+# Copyright (C) 2021, Red Hat Inc.\n+#\n+# SPDX-License-Identifier: LGPL-2.1-or-later\n+#\n+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>\n+\"\"\"dbus-doc is a Sphinx extension that provides documentation from D-Bus XML.\"\"\"\n+\n+from sphinx.application import Sphinx\n+from sphinx.util.docutils import SphinxDirective\n+from typing import Any, Dict\n+\n+\n+class FakeDBusDocDirective(SphinxDirective):\n+ has_content = True\n+ required_arguments = 1\n+\n+ def run(self):\n+ return []\n+\n+\n+def setup(app: Sphinx) -> Dict[str, Any]:\n+ \"\"\"Register a fake dbus-doc directive with Sphinx\"\"\"\n+ app.add_directive(\"dbus-doc\", FakeDBusDocDirective)\n", "prefixes": [ "PULL", "v2", "20/36" ] }