1*2668dc7bSMarc-André Lureau# D-Bus XML documentation extension 2*2668dc7bSMarc-André Lureau# 3*2668dc7bSMarc-André Lureau# Copyright (C) 2021, Red Hat Inc. 4*2668dc7bSMarc-André Lureau# 5*2668dc7bSMarc-André Lureau# SPDX-License-Identifier: LGPL-2.1-or-later 6*2668dc7bSMarc-André Lureau# 7*2668dc7bSMarc-André Lureau# Author: Marc-André Lureau <marcandre.lureau@redhat.com> 8*2668dc7bSMarc-André Lureau"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML.""" 9*2668dc7bSMarc-André Lureau 10*2668dc7bSMarc-André Lureauimport os 11*2668dc7bSMarc-André Lureauimport re 12*2668dc7bSMarc-André Lureaufrom typing import ( 13*2668dc7bSMarc-André Lureau TYPE_CHECKING, 14*2668dc7bSMarc-André Lureau Any, 15*2668dc7bSMarc-André Lureau Callable, 16*2668dc7bSMarc-André Lureau Dict, 17*2668dc7bSMarc-André Lureau Iterator, 18*2668dc7bSMarc-André Lureau List, 19*2668dc7bSMarc-André Lureau Optional, 20*2668dc7bSMarc-André Lureau Sequence, 21*2668dc7bSMarc-André Lureau Set, 22*2668dc7bSMarc-André Lureau Tuple, 23*2668dc7bSMarc-André Lureau Type, 24*2668dc7bSMarc-André Lureau TypeVar, 25*2668dc7bSMarc-André Lureau Union, 26*2668dc7bSMarc-André Lureau) 27*2668dc7bSMarc-André Lureau 28*2668dc7bSMarc-André Lureauimport sphinx 29*2668dc7bSMarc-André Lureaufrom docutils import nodes 30*2668dc7bSMarc-André Lureaufrom docutils.nodes import Element, Node 31*2668dc7bSMarc-André Lureaufrom docutils.parsers.rst import Directive, directives 32*2668dc7bSMarc-André Lureaufrom docutils.parsers.rst.states import RSTState 33*2668dc7bSMarc-André Lureaufrom docutils.statemachine import StringList, ViewList 34*2668dc7bSMarc-André Lureaufrom sphinx.application import Sphinx 35*2668dc7bSMarc-André Lureaufrom sphinx.errors import ExtensionError 36*2668dc7bSMarc-André Lureaufrom sphinx.util import logging 37*2668dc7bSMarc-André Lureaufrom sphinx.util.docstrings import prepare_docstring 38*2668dc7bSMarc-André Lureaufrom sphinx.util.docutils import SphinxDirective, switch_source_input 39*2668dc7bSMarc-André Lureaufrom sphinx.util.nodes import nested_parse_with_titles 40*2668dc7bSMarc-André Lureau 41*2668dc7bSMarc-André Lureauimport dbusdomain 42*2668dc7bSMarc-André Lureaufrom dbusparser import parse_dbus_xml 43*2668dc7bSMarc-André Lureau 44*2668dc7bSMarc-André Lureaulogger = logging.getLogger(__name__) 45*2668dc7bSMarc-André Lureau 46*2668dc7bSMarc-André Lureau__version__ = "1.0" 47*2668dc7bSMarc-André Lureau 48*2668dc7bSMarc-André Lureau 49*2668dc7bSMarc-André Lureauclass DBusDoc: 50*2668dc7bSMarc-André Lureau def __init__(self, sphinx_directive, dbusfile): 51*2668dc7bSMarc-André Lureau self._cur_doc = None 52*2668dc7bSMarc-André Lureau self._sphinx_directive = sphinx_directive 53*2668dc7bSMarc-André Lureau self._dbusfile = dbusfile 54*2668dc7bSMarc-André Lureau self._top_node = nodes.section() 55*2668dc7bSMarc-André Lureau self.result = StringList() 56*2668dc7bSMarc-André Lureau self.indent = "" 57*2668dc7bSMarc-André Lureau 58*2668dc7bSMarc-André Lureau def add_line(self, line: str, *lineno: int) -> None: 59*2668dc7bSMarc-André Lureau """Append one line of generated reST to the output.""" 60*2668dc7bSMarc-André Lureau if line.strip(): # not a blank line 61*2668dc7bSMarc-André Lureau self.result.append(self.indent + line, self._dbusfile, *lineno) 62*2668dc7bSMarc-André Lureau else: 63*2668dc7bSMarc-André Lureau self.result.append("", self._dbusfile, *lineno) 64*2668dc7bSMarc-André Lureau 65*2668dc7bSMarc-André Lureau def add_method(self, method): 66*2668dc7bSMarc-André Lureau self.add_line(f".. dbus:method:: {method.name}") 67*2668dc7bSMarc-André Lureau self.add_line("") 68*2668dc7bSMarc-André Lureau self.indent += " " 69*2668dc7bSMarc-André Lureau for arg in method.in_args: 70*2668dc7bSMarc-André Lureau self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}") 71*2668dc7bSMarc-André Lureau for arg in method.out_args: 72*2668dc7bSMarc-André Lureau self.add_line(f":ret {arg.signature} {arg.name}: {arg.doc_string}") 73*2668dc7bSMarc-André Lureau self.add_line("") 74*2668dc7bSMarc-André Lureau for line in prepare_docstring("\n" + method.doc_string): 75*2668dc7bSMarc-André Lureau self.add_line(line) 76*2668dc7bSMarc-André Lureau self.indent = self.indent[:-3] 77*2668dc7bSMarc-André Lureau 78*2668dc7bSMarc-André Lureau def add_signal(self, signal): 79*2668dc7bSMarc-André Lureau self.add_line(f".. dbus:signal:: {signal.name}") 80*2668dc7bSMarc-André Lureau self.add_line("") 81*2668dc7bSMarc-André Lureau self.indent += " " 82*2668dc7bSMarc-André Lureau for arg in signal.args: 83*2668dc7bSMarc-André Lureau self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}") 84*2668dc7bSMarc-André Lureau self.add_line("") 85*2668dc7bSMarc-André Lureau for line in prepare_docstring("\n" + signal.doc_string): 86*2668dc7bSMarc-André Lureau self.add_line(line) 87*2668dc7bSMarc-André Lureau self.indent = self.indent[:-3] 88*2668dc7bSMarc-André Lureau 89*2668dc7bSMarc-André Lureau def add_property(self, prop): 90*2668dc7bSMarc-André Lureau self.add_line(f".. dbus:property:: {prop.name}") 91*2668dc7bSMarc-André Lureau self.indent += " " 92*2668dc7bSMarc-André Lureau self.add_line(f":type: {prop.signature}") 93*2668dc7bSMarc-André Lureau access = {"read": "readonly", "write": "writeonly", "readwrite": "readwrite"}[ 94*2668dc7bSMarc-André Lureau prop.access 95*2668dc7bSMarc-André Lureau ] 96*2668dc7bSMarc-André Lureau self.add_line(f":{access}:") 97*2668dc7bSMarc-André Lureau if prop.emits_changed_signal: 98*2668dc7bSMarc-André Lureau self.add_line(f":emits-changed: yes") 99*2668dc7bSMarc-André Lureau self.add_line("") 100*2668dc7bSMarc-André Lureau for line in prepare_docstring("\n" + prop.doc_string): 101*2668dc7bSMarc-André Lureau self.add_line(line) 102*2668dc7bSMarc-André Lureau self.indent = self.indent[:-3] 103*2668dc7bSMarc-André Lureau 104*2668dc7bSMarc-André Lureau def add_interface(self, iface): 105*2668dc7bSMarc-André Lureau self.add_line(f".. dbus:interface:: {iface.name}") 106*2668dc7bSMarc-André Lureau self.add_line("") 107*2668dc7bSMarc-André Lureau self.indent += " " 108*2668dc7bSMarc-André Lureau for line in prepare_docstring("\n" + iface.doc_string): 109*2668dc7bSMarc-André Lureau self.add_line(line) 110*2668dc7bSMarc-André Lureau for method in iface.methods: 111*2668dc7bSMarc-André Lureau self.add_method(method) 112*2668dc7bSMarc-André Lureau for sig in iface.signals: 113*2668dc7bSMarc-André Lureau self.add_signal(sig) 114*2668dc7bSMarc-André Lureau for prop in iface.properties: 115*2668dc7bSMarc-André Lureau self.add_property(prop) 116*2668dc7bSMarc-André Lureau self.indent = self.indent[:-3] 117*2668dc7bSMarc-André Lureau 118*2668dc7bSMarc-André Lureau 119*2668dc7bSMarc-André Lureaudef parse_generated_content(state: RSTState, content: StringList) -> List[Node]: 120*2668dc7bSMarc-André Lureau """Parse a generated content by Documenter.""" 121*2668dc7bSMarc-André Lureau with switch_source_input(state, content): 122*2668dc7bSMarc-André Lureau node = nodes.paragraph() 123*2668dc7bSMarc-André Lureau node.document = state.document 124*2668dc7bSMarc-André Lureau state.nested_parse(content, 0, node) 125*2668dc7bSMarc-André Lureau 126*2668dc7bSMarc-André Lureau return node.children 127*2668dc7bSMarc-André Lureau 128*2668dc7bSMarc-André Lureau 129*2668dc7bSMarc-André Lureauclass DBusDocDirective(SphinxDirective): 130*2668dc7bSMarc-André Lureau """Extract documentation from the specified D-Bus XML file""" 131*2668dc7bSMarc-André Lureau 132*2668dc7bSMarc-André Lureau has_content = True 133*2668dc7bSMarc-André Lureau required_arguments = 1 134*2668dc7bSMarc-André Lureau optional_arguments = 0 135*2668dc7bSMarc-André Lureau final_argument_whitespace = True 136*2668dc7bSMarc-André Lureau 137*2668dc7bSMarc-André Lureau def run(self): 138*2668dc7bSMarc-André Lureau reporter = self.state.document.reporter 139*2668dc7bSMarc-André Lureau 140*2668dc7bSMarc-André Lureau try: 141*2668dc7bSMarc-André Lureau source, lineno = reporter.get_source_and_line(self.lineno) # type: ignore 142*2668dc7bSMarc-André Lureau except AttributeError: 143*2668dc7bSMarc-André Lureau source, lineno = (None, None) 144*2668dc7bSMarc-André Lureau 145*2668dc7bSMarc-André Lureau logger.debug("[dbusdoc] %s:%s: input:\n%s", source, lineno, self.block_text) 146*2668dc7bSMarc-André Lureau 147*2668dc7bSMarc-André Lureau env = self.state.document.settings.env 148*2668dc7bSMarc-André Lureau dbusfile = env.config.qapidoc_srctree + "/" + self.arguments[0] 149*2668dc7bSMarc-André Lureau with open(dbusfile, "rb") as f: 150*2668dc7bSMarc-André Lureau xml_data = f.read() 151*2668dc7bSMarc-André Lureau xml = parse_dbus_xml(xml_data) 152*2668dc7bSMarc-André Lureau doc = DBusDoc(self, dbusfile) 153*2668dc7bSMarc-André Lureau for iface in xml: 154*2668dc7bSMarc-André Lureau doc.add_interface(iface) 155*2668dc7bSMarc-André Lureau 156*2668dc7bSMarc-André Lureau result = parse_generated_content(self.state, doc.result) 157*2668dc7bSMarc-André Lureau return result 158*2668dc7bSMarc-André Lureau 159*2668dc7bSMarc-André Lureau 160*2668dc7bSMarc-André Lureaudef setup(app: Sphinx) -> Dict[str, Any]: 161*2668dc7bSMarc-André Lureau """Register dbus-doc directive with Sphinx""" 162*2668dc7bSMarc-André Lureau app.add_config_value("dbusdoc_srctree", None, "env") 163*2668dc7bSMarc-André Lureau app.add_directive("dbus-doc", DBusDocDirective) 164*2668dc7bSMarc-André Lureau dbusdomain.setup(app) 165*2668dc7bSMarc-André Lureau 166*2668dc7bSMarc-André Lureau return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True) 167