edk2: .git can be a file
[qemu.git] / docs / sphinx / dbusdoc.py
blobbe284ed08fd73b9ede9be6c82ff46d3eb325525f
1 # D-Bus XML documentation extension
3 # Copyright (C) 2021, Red Hat Inc.
5 # SPDX-License-Identifier: LGPL-2.1-or-later
7 # Author: Marc-André Lureau <marcandre.lureau@redhat.com>
8 """dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
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,
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
41 import dbusdomain
42 from dbusparser import parse_dbus_xml
44 logger = logging.getLogger(__name__)
46 __version__ = "1.0"
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 = ""
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)
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]
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]
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
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]
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]
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)
126 return node.children
129 class DBusDocDirective(SphinxDirective):
130 """Extract documentation from the specified D-Bus XML file"""
132 has_content = True
133 required_arguments = 1
134 optional_arguments = 0
135 final_argument_whitespace = True
137 def run(self):
138 reporter = self.state.document.reporter
140 try:
141 source, lineno = reporter.get_source_and_line(self.lineno) # type: ignore
142 except AttributeError:
143 source, lineno = (None, None)
145 logger.debug("[dbusdoc] %s:%s: input:\n%s", source, lineno, self.block_text)
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)
156 result = parse_generated_content(self.state, doc.result)
157 return result
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)
166 return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True)