xref: /openbmc/qemu/docs/sphinx/dbusdoc.py (revision 5316e12bb2b4408a1597b283ef4bb4794dd7b4f7)
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