qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

dbusdoc.py (5365B)


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