xref: /qemu/docs/sphinx/dbusparser.py (revision c5955f4f)
1# Based from "GDBus - GLib D-Bus Library":
2#
3# Copyright (C) 2008-2011 Red Hat, Inc.
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General
16# Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17#
18# Author: David Zeuthen <davidz@redhat.com>
19
20import xml.parsers.expat
21
22
23class Annotation:
24    def __init__(self, key, value):
25        self.key = key
26        self.value = value
27        self.annotations = []
28        self.since = ""
29
30
31class Arg:
32    def __init__(self, name, signature):
33        self.name = name
34        self.signature = signature
35        self.annotations = []
36        self.doc_string = ""
37        self.since = ""
38
39
40class Method:
41    def __init__(self, name, h_type_implies_unix_fd=True):
42        self.name = name
43        self.h_type_implies_unix_fd = h_type_implies_unix_fd
44        self.in_args = []
45        self.out_args = []
46        self.annotations = []
47        self.doc_string = ""
48        self.since = ""
49        self.deprecated = False
50        self.unix_fd = False
51
52
53class Signal:
54    def __init__(self, name):
55        self.name = name
56        self.args = []
57        self.annotations = []
58        self.doc_string = ""
59        self.since = ""
60        self.deprecated = False
61
62
63class Property:
64    def __init__(self, name, signature, access):
65        self.name = name
66        self.signature = signature
67        self.access = access
68        self.annotations = []
69        self.arg = Arg("value", self.signature)
70        self.arg.annotations = self.annotations
71        self.readable = False
72        self.writable = False
73        if self.access == "readwrite":
74            self.readable = True
75            self.writable = True
76        elif self.access == "read":
77            self.readable = True
78        elif self.access == "write":
79            self.writable = True
80        else:
81            raise ValueError('Invalid access type "{}"'.format(self.access))
82        self.doc_string = ""
83        self.since = ""
84        self.deprecated = False
85        self.emits_changed_signal = True
86
87
88class Interface:
89    def __init__(self, name):
90        self.name = name
91        self.methods = []
92        self.signals = []
93        self.properties = []
94        self.annotations = []
95        self.doc_string = ""
96        self.doc_string_brief = ""
97        self.since = ""
98        self.deprecated = False
99
100
101class DBusXMLParser:
102    STATE_TOP = "top"
103    STATE_NODE = "node"
104    STATE_INTERFACE = "interface"
105    STATE_METHOD = "method"
106    STATE_SIGNAL = "signal"
107    STATE_PROPERTY = "property"
108    STATE_ARG = "arg"
109    STATE_ANNOTATION = "annotation"
110    STATE_IGNORED = "ignored"
111
112    def __init__(self, xml_data, h_type_implies_unix_fd=True):
113        self._parser = xml.parsers.expat.ParserCreate()
114        self._parser.CommentHandler = self.handle_comment
115        self._parser.CharacterDataHandler = self.handle_char_data
116        self._parser.StartElementHandler = self.handle_start_element
117        self._parser.EndElementHandler = self.handle_end_element
118
119        self.parsed_interfaces = []
120        self._cur_object = None
121
122        self.state = DBusXMLParser.STATE_TOP
123        self.state_stack = []
124        self._cur_object = None
125        self._cur_object_stack = []
126
127        self.doc_comment_last_symbol = ""
128
129        self._h_type_implies_unix_fd = h_type_implies_unix_fd
130
131        self._parser.Parse(xml_data)
132
133    COMMENT_STATE_BEGIN = "begin"
134    COMMENT_STATE_PARAMS = "params"
135    COMMENT_STATE_BODY = "body"
136    COMMENT_STATE_SKIP = "skip"
137
138    def handle_comment(self, data):
139        comment_state = DBusXMLParser.COMMENT_STATE_BEGIN
140        lines = data.split("\n")
141        symbol = ""
142        body = ""
143        in_para = False
144        params = {}
145        for line in lines:
146            orig_line = line
147            line = line.lstrip()
148            if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN:
149                if len(line) > 0:
150                    colon_index = line.find(": ")
151                    if colon_index == -1:
152                        if line.endswith(":"):
153                            symbol = line[0 : len(line) - 1]
154                            comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
155                        else:
156                            comment_state = DBusXMLParser.COMMENT_STATE_SKIP
157                    else:
158                        symbol = line[0:colon_index]
159                        rest_of_line = line[colon_index + 2 :].strip()
160                        if len(rest_of_line) > 0:
161                            body += rest_of_line + "\n"
162                        comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
163            elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS:
164                if line.startswith("@"):
165                    colon_index = line.find(": ")
166                    if colon_index == -1:
167                        comment_state = DBusXMLParser.COMMENT_STATE_BODY
168                        if not in_para:
169                            in_para = True
170                        body += orig_line + "\n"
171                    else:
172                        param = line[1:colon_index]
173                        docs = line[colon_index + 2 :]
174                        params[param] = docs
175                else:
176                    comment_state = DBusXMLParser.COMMENT_STATE_BODY
177                    if len(line) > 0:
178                        if not in_para:
179                            in_para = True
180                        body += orig_line + "\n"
181            elif comment_state == DBusXMLParser.COMMENT_STATE_BODY:
182                if len(line) > 0:
183                    if not in_para:
184                        in_para = True
185                    body += orig_line + "\n"
186                else:
187                    if in_para:
188                        body += "\n"
189                        in_para = False
190        if in_para:
191            body += "\n"
192
193        if symbol != "":
194            self.doc_comment_last_symbol = symbol
195            self.doc_comment_params = params
196            self.doc_comment_body = body
197
198    def handle_char_data(self, data):
199        # print 'char_data=%s'%data
200        pass
201
202    def handle_start_element(self, name, attrs):
203        old_state = self.state
204        old_cur_object = self._cur_object
205        if self.state == DBusXMLParser.STATE_IGNORED:
206            self.state = DBusXMLParser.STATE_IGNORED
207        elif self.state == DBusXMLParser.STATE_TOP:
208            if name == DBusXMLParser.STATE_NODE:
209                self.state = DBusXMLParser.STATE_NODE
210            else:
211                self.state = DBusXMLParser.STATE_IGNORED
212        elif self.state == DBusXMLParser.STATE_NODE:
213            if name == DBusXMLParser.STATE_INTERFACE:
214                self.state = DBusXMLParser.STATE_INTERFACE
215                iface = Interface(attrs["name"])
216                self._cur_object = iface
217                self.parsed_interfaces.append(iface)
218            elif name == DBusXMLParser.STATE_ANNOTATION:
219                self.state = DBusXMLParser.STATE_ANNOTATION
220                anno = Annotation(attrs["name"], attrs["value"])
221                self._cur_object.annotations.append(anno)
222                self._cur_object = anno
223            else:
224                self.state = DBusXMLParser.STATE_IGNORED
225
226            # assign docs, if any
227            if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
228                self._cur_object.doc_string = self.doc_comment_body
229                if "short_description" in self.doc_comment_params:
230                    short_description = self.doc_comment_params["short_description"]
231                    self._cur_object.doc_string_brief = short_description
232                if "since" in self.doc_comment_params:
233                    self._cur_object.since = self.doc_comment_params["since"].strip()
234
235        elif self.state == DBusXMLParser.STATE_INTERFACE:
236            if name == DBusXMLParser.STATE_METHOD:
237                self.state = DBusXMLParser.STATE_METHOD
238                method = Method(
239                    attrs["name"], h_type_implies_unix_fd=self._h_type_implies_unix_fd
240                )
241                self._cur_object.methods.append(method)
242                self._cur_object = method
243            elif name == DBusXMLParser.STATE_SIGNAL:
244                self.state = DBusXMLParser.STATE_SIGNAL
245                signal = Signal(attrs["name"])
246                self._cur_object.signals.append(signal)
247                self._cur_object = signal
248            elif name == DBusXMLParser.STATE_PROPERTY:
249                self.state = DBusXMLParser.STATE_PROPERTY
250                prop = Property(attrs["name"], attrs["type"], attrs["access"])
251                self._cur_object.properties.append(prop)
252                self._cur_object = prop
253            elif name == DBusXMLParser.STATE_ANNOTATION:
254                self.state = DBusXMLParser.STATE_ANNOTATION
255                anno = Annotation(attrs["name"], attrs["value"])
256                self._cur_object.annotations.append(anno)
257                self._cur_object = anno
258            else:
259                self.state = DBusXMLParser.STATE_IGNORED
260
261            # assign docs, if any
262            if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
263                self._cur_object.doc_string = self.doc_comment_body
264                if "since" in self.doc_comment_params:
265                    self._cur_object.since = self.doc_comment_params["since"].strip()
266
267        elif self.state == DBusXMLParser.STATE_METHOD:
268            if name == DBusXMLParser.STATE_ARG:
269                self.state = DBusXMLParser.STATE_ARG
270                arg_name = None
271                if "name" in attrs:
272                    arg_name = attrs["name"]
273                arg = Arg(arg_name, attrs["type"])
274                direction = attrs.get("direction", "in")
275                if direction == "in":
276                    self._cur_object.in_args.append(arg)
277                elif direction == "out":
278                    self._cur_object.out_args.append(arg)
279                else:
280                    raise ValueError('Invalid direction "{}"'.format(direction))
281                self._cur_object = arg
282            elif name == DBusXMLParser.STATE_ANNOTATION:
283                self.state = DBusXMLParser.STATE_ANNOTATION
284                anno = Annotation(attrs["name"], attrs["value"])
285                self._cur_object.annotations.append(anno)
286                self._cur_object = anno
287            else:
288                self.state = DBusXMLParser.STATE_IGNORED
289
290            # assign docs, if any
291            if self.doc_comment_last_symbol == old_cur_object.name:
292                if "name" in attrs and attrs["name"] in self.doc_comment_params:
293                    doc_string = self.doc_comment_params[attrs["name"]]
294                    if doc_string is not None:
295                        self._cur_object.doc_string = doc_string
296                    if "since" in self.doc_comment_params:
297                        self._cur_object.since = self.doc_comment_params[
298                            "since"
299                        ].strip()
300
301        elif self.state == DBusXMLParser.STATE_SIGNAL:
302            if name == DBusXMLParser.STATE_ARG:
303                self.state = DBusXMLParser.STATE_ARG
304                arg_name = None
305                if "name" in attrs:
306                    arg_name = attrs["name"]
307                arg = Arg(arg_name, attrs["type"])
308                self._cur_object.args.append(arg)
309                self._cur_object = arg
310            elif name == DBusXMLParser.STATE_ANNOTATION:
311                self.state = DBusXMLParser.STATE_ANNOTATION
312                anno = Annotation(attrs["name"], attrs["value"])
313                self._cur_object.annotations.append(anno)
314                self._cur_object = anno
315            else:
316                self.state = DBusXMLParser.STATE_IGNORED
317
318            # assign docs, if any
319            if self.doc_comment_last_symbol == old_cur_object.name:
320                if "name" in attrs and attrs["name"] in self.doc_comment_params:
321                    doc_string = self.doc_comment_params[attrs["name"]]
322                    if doc_string is not None:
323                        self._cur_object.doc_string = doc_string
324                    if "since" in self.doc_comment_params:
325                        self._cur_object.since = self.doc_comment_params[
326                            "since"
327                        ].strip()
328
329        elif self.state == DBusXMLParser.STATE_PROPERTY:
330            if name == DBusXMLParser.STATE_ANNOTATION:
331                self.state = DBusXMLParser.STATE_ANNOTATION
332                anno = Annotation(attrs["name"], attrs["value"])
333                self._cur_object.annotations.append(anno)
334                self._cur_object = anno
335            else:
336                self.state = DBusXMLParser.STATE_IGNORED
337
338        elif self.state == DBusXMLParser.STATE_ARG:
339            if name == DBusXMLParser.STATE_ANNOTATION:
340                self.state = DBusXMLParser.STATE_ANNOTATION
341                anno = Annotation(attrs["name"], attrs["value"])
342                self._cur_object.annotations.append(anno)
343                self._cur_object = anno
344            else:
345                self.state = DBusXMLParser.STATE_IGNORED
346
347        elif self.state == DBusXMLParser.STATE_ANNOTATION:
348            if name == DBusXMLParser.STATE_ANNOTATION:
349                self.state = DBusXMLParser.STATE_ANNOTATION
350                anno = Annotation(attrs["name"], attrs["value"])
351                self._cur_object.annotations.append(anno)
352                self._cur_object = anno
353            else:
354                self.state = DBusXMLParser.STATE_IGNORED
355
356        else:
357            raise ValueError(
358                'Unhandled state "{}" while entering element with name "{}"'.format(
359                    self.state, name
360                )
361            )
362
363        self.state_stack.append(old_state)
364        self._cur_object_stack.append(old_cur_object)
365
366    def handle_end_element(self, name):
367        self.state = self.state_stack.pop()
368        self._cur_object = self._cur_object_stack.pop()
369
370
371def parse_dbus_xml(xml_data):
372    parser = DBusXMLParser(xml_data, True)
373    return parser.parsed_interfaces
374