Merge tag 'qemu-macppc-20230206' of https://github.com/mcayland/qemu into staging
[qemu.git] / docs / sphinx / dbusdomain.py
blob2ea95af623d2e3ccd916aefe1557f97bfb62cd83
1 # D-Bus sphinx domain 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>
9 from typing import (
10 Any,
11 Dict,
12 Iterable,
13 Iterator,
14 List,
15 NamedTuple,
16 Optional,
17 Tuple,
18 cast,
21 from docutils import nodes
22 from docutils.nodes import Element, Node
23 from docutils.parsers.rst import directives
24 from sphinx import addnodes
25 from sphinx.addnodes import desc_signature, pending_xref
26 from sphinx.directives import ObjectDescription
27 from sphinx.domains import Domain, Index, IndexEntry, ObjType
28 from sphinx.locale import _
29 from sphinx.roles import XRefRole
30 from sphinx.util import nodes as node_utils
31 from sphinx.util.docfields import Field, TypedField
32 from sphinx.util.typing import OptionSpec
35 class DBusDescription(ObjectDescription[str]):
36 """Base class for DBus objects"""
38 option_spec: OptionSpec = ObjectDescription.option_spec.copy()
39 option_spec.update(
41 "deprecated": directives.flag,
45 def get_index_text(self, modname: str, name: str) -> str:
46 """Return the text for the index entry of the object."""
47 raise NotImplementedError("must be implemented in subclasses")
49 def add_target_and_index(
50 self, name: str, sig: str, signode: desc_signature
51 ) -> None:
52 ifacename = self.env.ref_context.get("dbus:interface")
53 node_id = name
54 if ifacename:
55 node_id = f"{ifacename}.{node_id}"
57 signode["names"].append(name)
58 signode["ids"].append(node_id)
60 if "noindexentry" not in self.options:
61 indextext = self.get_index_text(ifacename, name)
62 if indextext:
63 self.indexnode["entries"].append(
64 ("single", indextext, node_id, "", None)
67 domain = cast(DBusDomain, self.env.get_domain("dbus"))
68 domain.note_object(name, self.objtype, node_id, location=signode)
71 class DBusInterface(DBusDescription):
72 """
73 Implementation of ``dbus:interface``.
74 """
76 def get_index_text(self, ifacename: str, name: str) -> str:
77 return ifacename
79 def before_content(self) -> None:
80 self.env.ref_context["dbus:interface"] = self.arguments[0]
82 def after_content(self) -> None:
83 self.env.ref_context.pop("dbus:interface")
85 def handle_signature(self, sig: str, signode: desc_signature) -> str:
86 signode += addnodes.desc_annotation("interface ", "interface ")
87 signode += addnodes.desc_name(sig, sig)
88 return sig
90 def run(self) -> List[Node]:
91 _, node = super().run()
92 name = self.arguments[0]
93 section = nodes.section(ids=[name + "-section"])
94 section += nodes.title(name, "%s interface" % name)
95 section += node
96 return [self.indexnode, section]
99 class DBusMember(DBusDescription):
101 signal = False
104 class DBusMethod(DBusMember):
106 Implementation of ``dbus:method``.
109 option_spec: OptionSpec = DBusMember.option_spec.copy()
110 option_spec.update(
112 "noreply": directives.flag,
116 doc_field_types: List[Field] = [
117 TypedField(
118 "arg",
119 label=_("Arguments"),
120 names=("arg",),
121 rolename="arg",
122 typerolename=None,
123 typenames=("argtype", "type"),
125 TypedField(
126 "ret",
127 label=_("Returns"),
128 names=("ret",),
129 rolename="ret",
130 typerolename=None,
131 typenames=("rettype", "type"),
135 def get_index_text(self, ifacename: str, name: str) -> str:
136 return _("%s() (%s method)") % (name, ifacename)
138 def handle_signature(self, sig: str, signode: desc_signature) -> str:
139 params = addnodes.desc_parameterlist()
140 returns = addnodes.desc_parameterlist()
142 contentnode = addnodes.desc_content()
143 self.state.nested_parse(self.content, self.content_offset, contentnode)
144 for child in contentnode:
145 if isinstance(child, nodes.field_list):
146 for field in child:
147 ty, sg, name = field[0].astext().split(None, 2)
148 param = addnodes.desc_parameter()
149 param += addnodes.desc_sig_keyword_type(sg, sg)
150 param += addnodes.desc_sig_space()
151 param += addnodes.desc_sig_name(name, name)
152 if ty == "arg":
153 params += param
154 elif ty == "ret":
155 returns += param
157 anno = "signal " if self.signal else "method "
158 signode += addnodes.desc_annotation(anno, anno)
159 signode += addnodes.desc_name(sig, sig)
160 signode += params
161 if not self.signal and "noreply" not in self.options:
162 ret = addnodes.desc_returns()
163 ret += returns
164 signode += ret
166 return sig
169 class DBusSignal(DBusMethod):
171 Implementation of ``dbus:signal``.
174 doc_field_types: List[Field] = [
175 TypedField(
176 "arg",
177 label=_("Arguments"),
178 names=("arg",),
179 rolename="arg",
180 typerolename=None,
181 typenames=("argtype", "type"),
184 signal = True
186 def get_index_text(self, ifacename: str, name: str) -> str:
187 return _("%s() (%s signal)") % (name, ifacename)
190 class DBusProperty(DBusMember):
192 Implementation of ``dbus:property``.
195 option_spec: OptionSpec = DBusMember.option_spec.copy()
196 option_spec.update(
198 "type": directives.unchanged,
199 "readonly": directives.flag,
200 "writeonly": directives.flag,
201 "readwrite": directives.flag,
202 "emits-changed": directives.unchanged,
206 doc_field_types: List[Field] = []
208 def get_index_text(self, ifacename: str, name: str) -> str:
209 return _("%s (%s property)") % (name, ifacename)
211 def transform_content(self, contentnode: addnodes.desc_content) -> None:
212 fieldlist = nodes.field_list()
213 access = None
214 if "readonly" in self.options:
215 access = _("read-only")
216 if "writeonly" in self.options:
217 access = _("write-only")
218 if "readwrite" in self.options:
219 access = _("read & write")
220 if access:
221 content = nodes.Text(access)
222 fieldname = nodes.field_name("", _("Access"))
223 fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
224 field = nodes.field("", fieldname, fieldbody)
225 fieldlist += field
226 emits = self.options.get("emits-changed", None)
227 if emits:
228 content = nodes.Text(emits)
229 fieldname = nodes.field_name("", _("Emits Changed"))
230 fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
231 field = nodes.field("", fieldname, fieldbody)
232 fieldlist += field
233 if len(fieldlist) > 0:
234 contentnode.insert(0, fieldlist)
236 def handle_signature(self, sig: str, signode: desc_signature) -> str:
237 contentnode = addnodes.desc_content()
238 self.state.nested_parse(self.content, self.content_offset, contentnode)
239 ty = self.options.get("type")
241 signode += addnodes.desc_annotation("property ", "property ")
242 signode += addnodes.desc_name(sig, sig)
243 signode += addnodes.desc_sig_punctuation("", ":")
244 signode += addnodes.desc_sig_keyword_type(ty, ty)
245 return sig
247 def run(self) -> List[Node]:
248 self.name = "dbus:member"
249 return super().run()
252 class DBusXRef(XRefRole):
253 def process_link(self, env, refnode, has_explicit_title, title, target):
254 refnode["dbus:interface"] = env.ref_context.get("dbus:interface")
255 if not has_explicit_title:
256 title = title.lstrip(".") # only has a meaning for the target
257 target = target.lstrip("~") # only has a meaning for the title
258 # if the first character is a tilde, don't display the module/class
259 # parts of the contents
260 if title[0:1] == "~":
261 title = title[1:]
262 dot = title.rfind(".")
263 if dot != -1:
264 title = title[dot + 1 :]
265 # if the first character is a dot, search more specific namespaces first
266 # else search builtins first
267 if target[0:1] == ".":
268 target = target[1:]
269 refnode["refspecific"] = True
270 return title, target
273 class DBusIndex(Index):
275 Index subclass to provide a D-Bus interfaces index.
278 name = "dbusindex"
279 localname = _("D-Bus Interfaces Index")
280 shortname = _("dbus")
282 def generate(
283 self, docnames: Iterable[str] = None
284 ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
285 content: Dict[str, List[IndexEntry]] = {}
286 # list of prefixes to ignore
287 ignores: List[str] = self.domain.env.config["dbus_index_common_prefix"]
288 ignores = sorted(ignores, key=len, reverse=True)
290 ifaces = sorted(
293 for x in self.domain.data["objects"].items()
294 if x[1].objtype == "interface"
296 key=lambda x: x[0].lower(),
298 for name, (docname, node_id, _) in ifaces:
299 if docnames and docname not in docnames:
300 continue
302 for ignore in ignores:
303 if name.startswith(ignore):
304 name = name[len(ignore) :]
305 stripped = ignore
306 break
307 else:
308 stripped = ""
310 entries = content.setdefault(name[0].lower(), [])
311 entries.append(IndexEntry(stripped + name, 0, docname, node_id, "", "", ""))
313 # sort by first letter
314 sorted_content = sorted(content.items())
316 return sorted_content, False
319 class ObjectEntry(NamedTuple):
320 docname: str
321 node_id: str
322 objtype: str
325 class DBusDomain(Domain):
327 Implementation of the D-Bus domain.
330 name = "dbus"
331 label = "D-Bus"
332 object_types: Dict[str, ObjType] = {
333 "interface": ObjType(_("interface"), "iface", "obj"),
334 "method": ObjType(_("method"), "meth", "obj"),
335 "signal": ObjType(_("signal"), "sig", "obj"),
336 "property": ObjType(_("property"), "attr", "_prop", "obj"),
338 directives = {
339 "interface": DBusInterface,
340 "method": DBusMethod,
341 "signal": DBusSignal,
342 "property": DBusProperty,
344 roles = {
345 "iface": DBusXRef(),
346 "meth": DBusXRef(),
347 "sig": DBusXRef(),
348 "prop": DBusXRef(),
350 initial_data: Dict[str, Dict[str, Tuple[Any]]] = {
351 "objects": {}, # fullname -> ObjectEntry
353 indices = [
354 DBusIndex,
357 @property
358 def objects(self) -> Dict[str, ObjectEntry]:
359 return self.data.setdefault("objects", {}) # fullname -> ObjectEntry
361 def note_object(
362 self, name: str, objtype: str, node_id: str, location: Any = None
363 ) -> None:
364 self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype)
366 def clear_doc(self, docname: str) -> None:
367 for fullname, obj in list(self.objects.items()):
368 if obj.docname == docname:
369 del self.objects[fullname]
371 def find_obj(self, typ: str, name: str) -> Optional[Tuple[str, ObjectEntry]]:
372 # skip parens
373 if name[-2:] == "()":
374 name = name[:-2]
375 if typ in ("meth", "sig", "prop"):
376 try:
377 ifacename, name = name.rsplit(".", 1)
378 except ValueError:
379 pass
380 return self.objects.get(name)
382 def resolve_xref(
383 self,
384 env: "BuildEnvironment",
385 fromdocname: str,
386 builder: "Builder",
387 typ: str,
388 target: str,
389 node: pending_xref,
390 contnode: Element,
391 ) -> Optional[Element]:
392 """Resolve the pending_xref *node* with the given *typ* and *target*."""
393 objdef = self.find_obj(typ, target)
394 if objdef:
395 return node_utils.make_refnode(
396 builder, fromdocname, objdef.docname, objdef.node_id, contnode
399 def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
400 for refname, obj in self.objects.items():
401 yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
404 def setup(app):
405 app.add_domain(DBusDomain)
406 app.add_config_value("dbus_index_common_prefix", [], "env")