2668dc7b5d
Add a new dbus-doc directive to import D-Bus interfaces documentation from the introspection XML. The comments annotations follow the gtkdoc/kerneldoc style, and should be formatted with reST. Note: I realize after the fact that I was implementing those modules with sphinx 4, and that we have much lower requirements. Instead of lowering the features and code (removing type annotations etc), let's have a warning in the documentation when the D-Bus modules can't be used, and point to the source XML file in that case. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Acked-by: Gerd Hoffmann <kraxel@redhat.com>
374 lines
14 KiB
Python
374 lines
14 KiB
Python
# Based from "GDBus - GLib D-Bus Library":
|
|
#
|
|
# Copyright (C) 2008-2011 Red Hat, Inc.
|
|
#
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
# License as published by the Free Software Foundation; either
|
|
# version 2.1 of the License, or (at your option) any later version.
|
|
#
|
|
# This library is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General
|
|
# Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# Author: David Zeuthen <davidz@redhat.com>
|
|
|
|
import xml.parsers.expat
|
|
|
|
|
|
class Annotation:
|
|
def __init__(self, key, value):
|
|
self.key = key
|
|
self.value = value
|
|
self.annotations = []
|
|
self.since = ""
|
|
|
|
|
|
class Arg:
|
|
def __init__(self, name, signature):
|
|
self.name = name
|
|
self.signature = signature
|
|
self.annotations = []
|
|
self.doc_string = ""
|
|
self.since = ""
|
|
|
|
|
|
class Method:
|
|
def __init__(self, name, h_type_implies_unix_fd=True):
|
|
self.name = name
|
|
self.h_type_implies_unix_fd = h_type_implies_unix_fd
|
|
self.in_args = []
|
|
self.out_args = []
|
|
self.annotations = []
|
|
self.doc_string = ""
|
|
self.since = ""
|
|
self.deprecated = False
|
|
self.unix_fd = False
|
|
|
|
|
|
class Signal:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.args = []
|
|
self.annotations = []
|
|
self.doc_string = ""
|
|
self.since = ""
|
|
self.deprecated = False
|
|
|
|
|
|
class Property:
|
|
def __init__(self, name, signature, access):
|
|
self.name = name
|
|
self.signature = signature
|
|
self.access = access
|
|
self.annotations = []
|
|
self.arg = Arg("value", self.signature)
|
|
self.arg.annotations = self.annotations
|
|
self.readable = False
|
|
self.writable = False
|
|
if self.access == "readwrite":
|
|
self.readable = True
|
|
self.writable = True
|
|
elif self.access == "read":
|
|
self.readable = True
|
|
elif self.access == "write":
|
|
self.writable = True
|
|
else:
|
|
raise ValueError('Invalid access type "{}"'.format(self.access))
|
|
self.doc_string = ""
|
|
self.since = ""
|
|
self.deprecated = False
|
|
self.emits_changed_signal = True
|
|
|
|
|
|
class Interface:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.methods = []
|
|
self.signals = []
|
|
self.properties = []
|
|
self.annotations = []
|
|
self.doc_string = ""
|
|
self.doc_string_brief = ""
|
|
self.since = ""
|
|
self.deprecated = False
|
|
|
|
|
|
class DBusXMLParser:
|
|
STATE_TOP = "top"
|
|
STATE_NODE = "node"
|
|
STATE_INTERFACE = "interface"
|
|
STATE_METHOD = "method"
|
|
STATE_SIGNAL = "signal"
|
|
STATE_PROPERTY = "property"
|
|
STATE_ARG = "arg"
|
|
STATE_ANNOTATION = "annotation"
|
|
STATE_IGNORED = "ignored"
|
|
|
|
def __init__(self, xml_data, h_type_implies_unix_fd=True):
|
|
self._parser = xml.parsers.expat.ParserCreate()
|
|
self._parser.CommentHandler = self.handle_comment
|
|
self._parser.CharacterDataHandler = self.handle_char_data
|
|
self._parser.StartElementHandler = self.handle_start_element
|
|
self._parser.EndElementHandler = self.handle_end_element
|
|
|
|
self.parsed_interfaces = []
|
|
self._cur_object = None
|
|
|
|
self.state = DBusXMLParser.STATE_TOP
|
|
self.state_stack = []
|
|
self._cur_object = None
|
|
self._cur_object_stack = []
|
|
|
|
self.doc_comment_last_symbol = ""
|
|
|
|
self._h_type_implies_unix_fd = h_type_implies_unix_fd
|
|
|
|
self._parser.Parse(xml_data)
|
|
|
|
COMMENT_STATE_BEGIN = "begin"
|
|
COMMENT_STATE_PARAMS = "params"
|
|
COMMENT_STATE_BODY = "body"
|
|
COMMENT_STATE_SKIP = "skip"
|
|
|
|
def handle_comment(self, data):
|
|
comment_state = DBusXMLParser.COMMENT_STATE_BEGIN
|
|
lines = data.split("\n")
|
|
symbol = ""
|
|
body = ""
|
|
in_para = False
|
|
params = {}
|
|
for line in lines:
|
|
orig_line = line
|
|
line = line.lstrip()
|
|
if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN:
|
|
if len(line) > 0:
|
|
colon_index = line.find(": ")
|
|
if colon_index == -1:
|
|
if line.endswith(":"):
|
|
symbol = line[0 : len(line) - 1]
|
|
comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
|
|
else:
|
|
comment_state = DBusXMLParser.COMMENT_STATE_SKIP
|
|
else:
|
|
symbol = line[0:colon_index]
|
|
rest_of_line = line[colon_index + 2 :].strip()
|
|
if len(rest_of_line) > 0:
|
|
body += rest_of_line + "\n"
|
|
comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
|
|
elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS:
|
|
if line.startswith("@"):
|
|
colon_index = line.find(": ")
|
|
if colon_index == -1:
|
|
comment_state = DBusXMLParser.COMMENT_STATE_BODY
|
|
if not in_para:
|
|
in_para = True
|
|
body += orig_line + "\n"
|
|
else:
|
|
param = line[1:colon_index]
|
|
docs = line[colon_index + 2 :]
|
|
params[param] = docs
|
|
else:
|
|
comment_state = DBusXMLParser.COMMENT_STATE_BODY
|
|
if len(line) > 0:
|
|
if not in_para:
|
|
in_para = True
|
|
body += orig_line + "\n"
|
|
elif comment_state == DBusXMLParser.COMMENT_STATE_BODY:
|
|
if len(line) > 0:
|
|
if not in_para:
|
|
in_para = True
|
|
body += orig_line + "\n"
|
|
else:
|
|
if in_para:
|
|
body += "\n"
|
|
in_para = False
|
|
if in_para:
|
|
body += "\n"
|
|
|
|
if symbol != "":
|
|
self.doc_comment_last_symbol = symbol
|
|
self.doc_comment_params = params
|
|
self.doc_comment_body = body
|
|
|
|
def handle_char_data(self, data):
|
|
# print 'char_data=%s'%data
|
|
pass
|
|
|
|
def handle_start_element(self, name, attrs):
|
|
old_state = self.state
|
|
old_cur_object = self._cur_object
|
|
if self.state == DBusXMLParser.STATE_IGNORED:
|
|
self.state = DBusXMLParser.STATE_IGNORED
|
|
elif self.state == DBusXMLParser.STATE_TOP:
|
|
if name == DBusXMLParser.STATE_NODE:
|
|
self.state = DBusXMLParser.STATE_NODE
|
|
else:
|
|
self.state = DBusXMLParser.STATE_IGNORED
|
|
elif self.state == DBusXMLParser.STATE_NODE:
|
|
if name == DBusXMLParser.STATE_INTERFACE:
|
|
self.state = DBusXMLParser.STATE_INTERFACE
|
|
iface = Interface(attrs["name"])
|
|
self._cur_object = iface
|
|
self.parsed_interfaces.append(iface)
|
|
elif name == DBusXMLParser.STATE_ANNOTATION:
|
|
self.state = DBusXMLParser.STATE_ANNOTATION
|
|
anno = Annotation(attrs["name"], attrs["value"])
|
|
self._cur_object.annotations.append(anno)
|
|
self._cur_object = anno
|
|
else:
|
|
self.state = DBusXMLParser.STATE_IGNORED
|
|
|
|
# assign docs, if any
|
|
if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
|
|
self._cur_object.doc_string = self.doc_comment_body
|
|
if "short_description" in self.doc_comment_params:
|
|
short_description = self.doc_comment_params["short_description"]
|
|
self._cur_object.doc_string_brief = short_description
|
|
if "since" in self.doc_comment_params:
|
|
self._cur_object.since = self.doc_comment_params["since"].strip()
|
|
|
|
elif self.state == DBusXMLParser.STATE_INTERFACE:
|
|
if name == DBusXMLParser.STATE_METHOD:
|
|
self.state = DBusXMLParser.STATE_METHOD
|
|
method = Method(
|
|
attrs["name"], h_type_implies_unix_fd=self._h_type_implies_unix_fd
|
|
)
|
|
self._cur_object.methods.append(method)
|
|
self._cur_object = method
|
|
elif name == DBusXMLParser.STATE_SIGNAL:
|
|
self.state = DBusXMLParser.STATE_SIGNAL
|
|
signal = Signal(attrs["name"])
|
|
self._cur_object.signals.append(signal)
|
|
self._cur_object = signal
|
|
elif name == DBusXMLParser.STATE_PROPERTY:
|
|
self.state = DBusXMLParser.STATE_PROPERTY
|
|
prop = Property(attrs["name"], attrs["type"], attrs["access"])
|
|
self._cur_object.properties.append(prop)
|
|
self._cur_object = prop
|
|
elif name == DBusXMLParser.STATE_ANNOTATION:
|
|
self.state = DBusXMLParser.STATE_ANNOTATION
|
|
anno = Annotation(attrs["name"], attrs["value"])
|
|
self._cur_object.annotations.append(anno)
|
|
self._cur_object = anno
|
|
else:
|
|
self.state = DBusXMLParser.STATE_IGNORED
|
|
|
|
# assign docs, if any
|
|
if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
|
|
self._cur_object.doc_string = self.doc_comment_body
|
|
if "since" in self.doc_comment_params:
|
|
self._cur_object.since = self.doc_comment_params["since"].strip()
|
|
|
|
elif self.state == DBusXMLParser.STATE_METHOD:
|
|
if name == DBusXMLParser.STATE_ARG:
|
|
self.state = DBusXMLParser.STATE_ARG
|
|
arg_name = None
|
|
if "name" in attrs:
|
|
arg_name = attrs["name"]
|
|
arg = Arg(arg_name, attrs["type"])
|
|
direction = attrs.get("direction", "in")
|
|
if direction == "in":
|
|
self._cur_object.in_args.append(arg)
|
|
elif direction == "out":
|
|
self._cur_object.out_args.append(arg)
|
|
else:
|
|
raise ValueError('Invalid direction "{}"'.format(direction))
|
|
self._cur_object = arg
|
|
elif name == DBusXMLParser.STATE_ANNOTATION:
|
|
self.state = DBusXMLParser.STATE_ANNOTATION
|
|
anno = Annotation(attrs["name"], attrs["value"])
|
|
self._cur_object.annotations.append(anno)
|
|
self._cur_object = anno
|
|
else:
|
|
self.state = DBusXMLParser.STATE_IGNORED
|
|
|
|
# assign docs, if any
|
|
if self.doc_comment_last_symbol == old_cur_object.name:
|
|
if "name" in attrs and attrs["name"] in self.doc_comment_params:
|
|
doc_string = self.doc_comment_params[attrs["name"]]
|
|
if doc_string is not None:
|
|
self._cur_object.doc_string = doc_string
|
|
if "since" in self.doc_comment_params:
|
|
self._cur_object.since = self.doc_comment_params[
|
|
"since"
|
|
].strip()
|
|
|
|
elif self.state == DBusXMLParser.STATE_SIGNAL:
|
|
if name == DBusXMLParser.STATE_ARG:
|
|
self.state = DBusXMLParser.STATE_ARG
|
|
arg_name = None
|
|
if "name" in attrs:
|
|
arg_name = attrs["name"]
|
|
arg = Arg(arg_name, attrs["type"])
|
|
self._cur_object.args.append(arg)
|
|
self._cur_object = arg
|
|
elif name == DBusXMLParser.STATE_ANNOTATION:
|
|
self.state = DBusXMLParser.STATE_ANNOTATION
|
|
anno = Annotation(attrs["name"], attrs["value"])
|
|
self._cur_object.annotations.append(anno)
|
|
self._cur_object = anno
|
|
else:
|
|
self.state = DBusXMLParser.STATE_IGNORED
|
|
|
|
# assign docs, if any
|
|
if self.doc_comment_last_symbol == old_cur_object.name:
|
|
if "name" in attrs and attrs["name"] in self.doc_comment_params:
|
|
doc_string = self.doc_comment_params[attrs["name"]]
|
|
if doc_string is not None:
|
|
self._cur_object.doc_string = doc_string
|
|
if "since" in self.doc_comment_params:
|
|
self._cur_object.since = self.doc_comment_params[
|
|
"since"
|
|
].strip()
|
|
|
|
elif self.state == DBusXMLParser.STATE_PROPERTY:
|
|
if name == DBusXMLParser.STATE_ANNOTATION:
|
|
self.state = DBusXMLParser.STATE_ANNOTATION
|
|
anno = Annotation(attrs["name"], attrs["value"])
|
|
self._cur_object.annotations.append(anno)
|
|
self._cur_object = anno
|
|
else:
|
|
self.state = DBusXMLParser.STATE_IGNORED
|
|
|
|
elif self.state == DBusXMLParser.STATE_ARG:
|
|
if name == DBusXMLParser.STATE_ANNOTATION:
|
|
self.state = DBusXMLParser.STATE_ANNOTATION
|
|
anno = Annotation(attrs["name"], attrs["value"])
|
|
self._cur_object.annotations.append(anno)
|
|
self._cur_object = anno
|
|
else:
|
|
self.state = DBusXMLParser.STATE_IGNORED
|
|
|
|
elif self.state == DBusXMLParser.STATE_ANNOTATION:
|
|
if name == DBusXMLParser.STATE_ANNOTATION:
|
|
self.state = DBusXMLParser.STATE_ANNOTATION
|
|
anno = Annotation(attrs["name"], attrs["value"])
|
|
self._cur_object.annotations.append(anno)
|
|
self._cur_object = anno
|
|
else:
|
|
self.state = DBusXMLParser.STATE_IGNORED
|
|
|
|
else:
|
|
raise ValueError(
|
|
'Unhandled state "{}" while entering element with name "{}"'.format(
|
|
self.state, name
|
|
)
|
|
)
|
|
|
|
self.state_stack.append(old_state)
|
|
self._cur_object_stack.append(old_cur_object)
|
|
|
|
def handle_end_element(self, name):
|
|
self.state = self.state_stack.pop()
|
|
self._cur_object = self._cur_object_stack.pop()
|
|
|
|
|
|
def parse_dbus_xml(xml_data):
|
|
parser = DBusXMLParser(xml_data, True)
|
|
return parser.parsed_interfaces
|