qemu

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

dbusdomain.py (12737B)


      1 # D-Bus sphinx domain 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 
      9 from typing import (
     10     Any,
     11     Dict,
     12     Iterable,
     13     Iterator,
     14     List,
     15     NamedTuple,
     16     Optional,
     17     Tuple,
     18     cast,
     19 )
     20 
     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
     33 
     34 
     35 class DBusDescription(ObjectDescription[str]):
     36     """Base class for DBus objects"""
     37 
     38     option_spec: OptionSpec = ObjectDescription.option_spec.copy()
     39     option_spec.update(
     40         {
     41             "deprecated": directives.flag,
     42         }
     43     )
     44 
     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")
     48 
     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}"
     56 
     57         signode["names"].append(name)
     58         signode["ids"].append(node_id)
     59 
     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)
     65                 )
     66 
     67         domain = cast(DBusDomain, self.env.get_domain("dbus"))
     68         domain.note_object(name, self.objtype, node_id, location=signode)
     69 
     70 
     71 class DBusInterface(DBusDescription):
     72     """
     73     Implementation of ``dbus:interface``.
     74     """
     75 
     76     def get_index_text(self, ifacename: str, name: str) -> str:
     77         return ifacename
     78 
     79     def before_content(self) -> None:
     80         self.env.ref_context["dbus:interface"] = self.arguments[0]
     81 
     82     def after_content(self) -> None:
     83         self.env.ref_context.pop("dbus:interface")
     84 
     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
     89 
     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]
     97 
     98 
     99 class DBusMember(DBusDescription):
    100 
    101     signal = False
    102 
    103 
    104 class DBusMethod(DBusMember):
    105     """
    106     Implementation of ``dbus:method``.
    107     """
    108 
    109     option_spec: OptionSpec = DBusMember.option_spec.copy()
    110     option_spec.update(
    111         {
    112             "noreply": directives.flag,
    113         }
    114     )
    115 
    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"),
    124         ),
    125         TypedField(
    126             "ret",
    127             label=_("Returns"),
    128             names=("ret",),
    129             rolename="ret",
    130             typerolename=None,
    131             typenames=("rettype", "type"),
    132         ),
    133     ]
    134 
    135     def get_index_text(self, ifacename: str, name: str) -> str:
    136         return _("%s() (%s method)") % (name, ifacename)
    137 
    138     def handle_signature(self, sig: str, signode: desc_signature) -> str:
    139         params = addnodes.desc_parameterlist()
    140         returns = addnodes.desc_parameterlist()
    141 
    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
    156 
    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
    165 
    166         return sig
    167 
    168 
    169 class DBusSignal(DBusMethod):
    170     """
    171     Implementation of ``dbus:signal``.
    172     """
    173 
    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"),
    182         ),
    183     ]
    184     signal = True
    185 
    186     def get_index_text(self, ifacename: str, name: str) -> str:
    187         return _("%s() (%s signal)") % (name, ifacename)
    188 
    189 
    190 class DBusProperty(DBusMember):
    191     """
    192     Implementation of ``dbus:property``.
    193     """
    194 
    195     option_spec: OptionSpec = DBusMember.option_spec.copy()
    196     option_spec.update(
    197         {
    198             "type": directives.unchanged,
    199             "readonly": directives.flag,
    200             "writeonly": directives.flag,
    201             "readwrite": directives.flag,
    202             "emits-changed": directives.unchanged,
    203         }
    204     )
    205 
    206     doc_field_types: List[Field] = []
    207 
    208     def get_index_text(self, ifacename: str, name: str) -> str:
    209         return _("%s (%s property)") % (name, ifacename)
    210 
    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)
    235 
    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")
    240 
    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
    246 
    247     def run(self) -> List[Node]:
    248         self.name = "dbus:member"
    249         return super().run()
    250 
    251 
    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
    271 
    272 
    273 class DBusIndex(Index):
    274     """
    275     Index subclass to provide a D-Bus interfaces index.
    276     """
    277 
    278     name = "dbusindex"
    279     localname = _("D-Bus Interfaces Index")
    280     shortname = _("dbus")
    281 
    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)
    289 
    290         ifaces = sorted(
    291             [
    292                 x
    293                 for x in self.domain.data["objects"].items()
    294                 if x[1].objtype == "interface"
    295             ],
    296             key=lambda x: x[0].lower(),
    297         )
    298         for name, (docname, node_id, _) in ifaces:
    299             if docnames and docname not in docnames:
    300                 continue
    301 
    302             for ignore in ignores:
    303                 if name.startswith(ignore):
    304                     name = name[len(ignore) :]
    305                     stripped = ignore
    306                     break
    307             else:
    308                 stripped = ""
    309 
    310             entries = content.setdefault(name[0].lower(), [])
    311             entries.append(IndexEntry(stripped + name, 0, docname, node_id, "", "", ""))
    312 
    313         # sort by first letter
    314         sorted_content = sorted(content.items())
    315 
    316         return sorted_content, False
    317 
    318 
    319 class ObjectEntry(NamedTuple):
    320     docname: str
    321     node_id: str
    322     objtype: str
    323 
    324 
    325 class DBusDomain(Domain):
    326     """
    327     Implementation of the D-Bus domain.
    328     """
    329 
    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"),
    337     }
    338     directives = {
    339         "interface": DBusInterface,
    340         "method": DBusMethod,
    341         "signal": DBusSignal,
    342         "property": DBusProperty,
    343     }
    344     roles = {
    345         "iface": DBusXRef(),
    346         "meth": DBusXRef(),
    347         "sig": DBusXRef(),
    348         "prop": DBusXRef(),
    349     }
    350     initial_data: Dict[str, Dict[str, Tuple[Any]]] = {
    351         "objects": {},  # fullname -> ObjectEntry
    352     }
    353     indices = [
    354         DBusIndex,
    355     ]
    356 
    357     @property
    358     def objects(self) -> Dict[str, ObjectEntry]:
    359         return self.data.setdefault("objects", {})  # fullname -> ObjectEntry
    360 
    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)
    365 
    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]
    370 
    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)
    381 
    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
    397             )
    398 
    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)
    402 
    403 
    404 def setup(app):
    405     app.add_domain(DBusDomain)
    406     app.add_config_value("dbus_index_common_prefix", [], "env")