diff options
author | David Zeuthen <davidz@redhat.com> | 2011-02-14 07:05:32 -0500 |
---|---|---|
committer | David Zeuthen <davidz@redhat.com> | 2011-02-14 07:05:32 -0500 |
commit | 55f2c2cbc44b45150be7d1d8a257b5823ebba902 (patch) | |
tree | 7dc21346cb8041f927ae5b2df463b7422b31587f | |
parent | fc056dfd5dbd87aa40ea20e707d03d8e50600198 (diff) |
Rewrite D-Bus XML parser in python instead of using the one in libgio
Ideally we'd use the one in libgio but unfortunately
gobject-introspection doesn't expose struct field access and we also
want to avoid potential boot-strapping problems if we want to use the
codegen in libgio itself.
(Using the codegen in libgio itself, btw, is not far fetched; at least
we could generate C code for standard well-known interfaces including
(but not limited to) org.freedesktop.DBus itself).
Signed-off-by: David Zeuthen <davidz@redhat.com>
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/codegen.py | 96 | ||||
-rw-r--r-- | src/dbustypes.py | 134 | ||||
-rw-r--r-- | src/parser.py | 152 |
4 files changed, 301 insertions, 84 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 6aa7e83..869a5ef 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,7 @@ codegen_PYTHON = \ codegen.py \ config.py \ dbustypes.py \ + parser.py \ utils.py \ $(NULL) @@ -86,7 +87,7 @@ libgdbus_codegen_la_LIBADD = \ BUILT_SOURCES += test-generated.h -test-generated.h : Makefile.am org.project.xml codegen.py +test-generated.h : Makefile.am org.project.xml $(codegen_PYTHON) $(PYTHON) ./codegen.py \ --namespace Foo \ --include-dir src \ diff --git a/src/codegen.py b/src/codegen.py index 88d1451..9f36129 100644 --- a/src/codegen.py +++ b/src/codegen.py @@ -3,11 +3,10 @@ import sys import argparse -from gi.repository import Gio - import config import utils import dbustypes +import parser # ---------------------------------------------------------------------------------------------------- @@ -524,7 +523,7 @@ class CodeGenerator: ' -1,\n' ' "%s",\n' ' "%s",\n'%(prefix, n, a.key, a.value)) - if len(a.get_annotations()) == 0: + if len(a.annotations) == 0: self.c.write(' NULL\n') else: self.c.write(' (GDBusAnnotationInfo **) &%s_%d_pointers\n'%(prefix, n)) @@ -2155,6 +2154,24 @@ def find_arg(arg_list, arg_name): return a return None +def find_method(iface, method): + for m in iface.methods: + if m.name == method: + return m + return None + +def find_signal(iface, signal): + for m in iface.signals: + if m.name == signal: + return m + return None + +def find_prop(iface, prop): + for m in iface.properties: + if m.name == prop: + return m + return None + def apply_annotation(iface_list, iface, method, signal, prop, arg, key, value): for i in iface_list: if i.name == iface: @@ -2167,37 +2184,37 @@ def apply_annotation(iface_list, iface, method, signal, prop, arg, key, value): target_obj = None if method: - method_obj = iface_obj.lookup_method(method) + method_obj = find_method(iface_obj, method) if method_obj == None: raise RuntimeError('No method %s on interface %s'%(method, iface)) if arg: - arg_obj = find_arg(method_obj.get_in_args(), arg) + arg_obj = find_arg(method_obj.in_args, arg) if (arg_obj == None): - arg_obj = find_arg(method_obj.get_out_args(), arg) + arg_obj = find_arg(method_obj.out_args, arg) if (arg_obj == None): raise RuntimeError('No arg %s on method %s on interface %s'%(arg, method, iface)) target_obj = arg_obj else: target_obj = method_obj elif signal: - signal_obj = iface_obj.lookup_signal(signal) + signal_obj = find_signal(iface_obj, signal) if signal_obj == None: raise RuntimeError('No signal %s on interface %s'%(signal, iface)) if arg: - arg_obj = find_arg(signal_obj.get_args(), arg) + arg_obj = find_arg(signal_obj.args, arg) if (arg_obj == None): raise RuntimeError('No arg %s on signal %s on interface %s'%(arg, signal, iface)) target_obj = arg_obj else: target_obj = signal_obj elif prop: - prop_obj = iface_obj.lookup_property(prop) + prop_obj = find_prop(iface_obj, prop) if prop_obj == None: raise RuntimeError('No property %s on interface %s'%(prop, iface)) target_obj = prop_obj else: target_obj = iface_obj - target_obj.add_annotation(key, value) + target_obj.annotations.append(dbustypes.Annotation(key, value)) def apply_annotations(iface_list, annotation_list): @@ -2246,35 +2263,44 @@ def apply_annotations(iface_list, annotation_list): apply_annotation(iface_list, iface, None, None, None, None, key, value) def codegen_main(): - parser = argparse.ArgumentParser(description='GDBus Code Generator') - parser.add_argument('xml_files', metavar='FILE', type=file, nargs='+', - help='D-Bus introspection XML file') - parser.add_argument('--namespace', nargs='?', metavar='NAMESPACE', default='', - help='The namespace to use for generated code') - parser.add_argument('--output-hfile', nargs='?', metavar='HFILE', default='generated.h', - help='Name of C header file to generate.') - parser.add_argument('--output-cfile', nargs='?', metavar='CFILE', default='generated.c', - help='Name of C header file to generate.') - parser.add_argument('--strip-prefix', nargs='?', metavar='PREFIX', default='', - help='String to strip from D-Bus names.') - parser.add_argument('--include-dir', nargs='?', metavar='DIR', - help='Directory to prefix #include directives with.') - parser.add_argument('--add-include', nargs='?', action='append', metavar='FILE', - help='Include header file (may be used several times).') - parser.add_argument('--annotate', nargs=3, action='append', metavar=('WHAT', 'KEY', 'VALUE'), - help='Add annotation (may be used several times).') - args = parser.parse_args(); + arg_parser = argparse.ArgumentParser(description='GDBus Code Generator') + arg_parser.add_argument('xml_files', metavar='FILE', type=file, nargs='+', + help='D-Bus introspection XML file') + arg_parser.add_argument('--namespace', nargs='?', metavar='NAMESPACE', default='', + help='The namespace to use for generated code') + arg_parser.add_argument('--output-hfile', nargs='?', metavar='HFILE', default='generated.h', + help='Name of C header file to generate.') + arg_parser.add_argument('--output-cfile', nargs='?', metavar='CFILE', default='generated.c', + help='Name of C header file to generate.') + arg_parser.add_argument('--strip-prefix', nargs='?', metavar='PREFIX', default='', + help='String to strip from D-Bus names.') + arg_parser.add_argument('--include-dir', nargs='?', metavar='DIR', + help='Directory to prefix #include directives with.') + arg_parser.add_argument('--add-include', nargs='?', action='append', metavar='FILE', + help='Include header file (may be used several times).') + arg_parser.add_argument('--annotate', nargs=3, action='append', metavar=('WHAT', 'KEY', 'VALUE'), + help='Add annotation (may be used several times).') + args = arg_parser.parse_args(); all_ifaces = [] for f in args.xml_files: - xml = f.read() + xml_data = f.read() f.close() - node_info = Gio.DBusNodeInfo.new_for_xml(xml) - gdbus_interfaces = node_info.get_interfaces() - if args.annotate != None: - apply_annotations(gdbus_interfaces, args.annotate) - for gi in gdbus_interfaces: - all_ifaces.append(dbustypes.Interface(gi, args.strip_prefix, args.namespace)) + parsed_ifaces = parser.parse_dbus_xml(xml_data, args.strip_prefix, args.namespace) + # TODO: apply_annotations + all_ifaces.extend(parsed_ifaces) + + apply_annotations(all_ifaces, args.annotate) + + #node_info = Gio.DBusNodeInfo.new_for_xml(xml_data) + #gdbus_interfaces = node_info.get_interfaces() + #if args.annotate != None: + # apply_annotations(gdbus_interfaces, args.annotate) + #for gi in gdbus_interfaces: + # all_ifaces.append(dbustypes.Interface(gi, args.strip_prefix, args.namespace)) + + for i in parsed_ifaces: + i.calculate_c_names(args.strip_prefix, args.namespace) h = file(args.output_hfile, 'w') c = file(args.output_cfile, 'w') diff --git a/src/dbustypes.py b/src/dbustypes.py index dc4304b..89ae810 100644 --- a/src/dbustypes.py +++ b/src/dbustypes.py @@ -6,22 +6,24 @@ class Annotation: def __init__(self, key, value): self.key = key self.value = value + self.annotations = [] class Arg: - def __init__(self, name, signature, annotations): + def __init__(self, name, signature): self.name = name - self.annotations = annotations - self.signature = signature + self.annotations = [] + + def calculate_c_names(self, strip_prefix, namespace): # default to GVariant self.ctype_in_g = 'GVariant *' self.ctype_in = 'GVariant *' self.ctype_out = 'GVariant **' self.gtype = 'G_TYPE_VARIANT' self.free_func = 'g_variant_unref' - self.format_in = '@' + signature - self.format_out = '@' + signature - if not utils.lookup_annotation(annotations, 'org.gtk.GDBus.UseGVariant'): + self.format_in = '@' + self.signature + self.format_out = '@' + self.signature + if not utils.lookup_annotation(self.annotations, 'org.gtk.GDBus.UseGVariant'): if self.signature == 'b': self.ctype_in_g = 'gboolean ' self.ctype_in = 'gboolean ' @@ -145,50 +147,74 @@ class Arg: #print ' arg: %s (sig=%s, ctype_in=%s, ctype_out=%s, gtype=%s)'%(self.name, self.signature, self.ctype_in, self.ctype_out, self.gtype) class Method: - def __init__(self, gm): - self.name = gm.name - self.annotations = gm.get_annotations() + def __init__(self, name): + self.name = name + self.in_args = [] + self.out_args = [] + self.annotations = [] + + def calculate_c_names(self, strip_prefix, namespace): self.name_lower = utils.camel_case_to_uscore(self.name).lower() self.name_hyphen = self.name_lower.replace('_', '-') + + for a in self.in_args: + a.calculate_c_names(strip_prefix, namespace) + + for a in self.out_args: + a.calculate_c_names(strip_prefix, namespace) #print ' --- method' #print ' name: ' + self.name #print ' name_lower: ' + self.name_lower - self.in_args = [] - gdbus_in_args = gm.get_in_args() - for a in gdbus_in_args: - self.in_args.append(Arg(a.name, a.signature, a.get_annotations())) + #self.in_args = [] + #gdbus_in_args = gm.get_in_args() + #for a in gdbus_in_args: + # self.in_args.append(Arg(a.name, a.signature, a.get_annotations())) - self.out_args = [] - gdbus_out_args = gm.get_out_args() - for a in gdbus_out_args: - self.out_args.append(Arg(a.name, a.signature, a.get_annotations())) + #self.out_args = [] + #gdbus_out_args = gm.get_out_args() + #for a in gdbus_out_args: + # self.out_args.append(Arg(a.name, a.signature, a.get_annotations())) class Signal: - def __init__(self, gs): - self.name = gs.name - self.annotations = gs.get_annotations() + def __init__(self, name): + self.name = name + self.args = [] + self.annotations = [] + + def calculate_c_names(self, strip_prefix, namespace): self.name_lower = utils.camel_case_to_uscore(self.name).lower() self.name_hyphen = self.name_lower.replace('_', '-') #print ' --- signal' #print ' name: ' + self.name #print ' name_lower: ' + self.name_lower - self.args = [] - gdbus_args = gs.get_args() - for a in gdbus_args: - self.args.append(Arg(a.name, a.signature, a.get_annotations())) + for a in self.args: + a.calculate_c_names(strip_prefix, namespace) class Property: - def __init__(self, gp): - self.name = gp.name - self.annotations = gp.get_annotations() + 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 RuntimeError('Invalid access type %s'%self.access) + + def calculate_c_names(self, strip_prefix, namespace): self.name_lower = utils.camel_case_to_uscore(self.name).lower() self.name_hyphen = self.name_lower.replace('_', '-') - flags = gp.get_flags() - # TODO: use constants from Gio.DBusPropertyInfoFlags bitfield - self.readable = ((flags & 1) != 0) - self.writable = ((flags & 2) != 0) #print ' --- property' #print ' name: ' + self.name #print ' name_lower: ' + self.name_lower @@ -196,14 +222,20 @@ class Property: #print ' readable: %d'%self.readable #print ' writable: %d'%self.writable - self.arg = Arg('value', gp.signature, gp.get_annotations()) + # recalculate arg + self.arg.annotations = self.annotations + self.arg.calculate_c_names(strip_prefix, namespace) class Interface: - def __init__(self, gi, strip_prefix, namespace): - self.name = gi.name - self.annotations = gi.get_annotations() + def __init__(self, name): #gi, strip_prefix, namespace): + self.name = name + self.methods = [] + self.signals = [] + self.properties = [] + self.annotations = [] - s = gi.name + def calculate_c_names(self, strip_prefix, namespace): + s = self.name if s.startswith(strip_prefix): s = s[len(strip_prefix):] s = utils.strip_dots(s) @@ -217,6 +249,15 @@ class Interface: self.name_upper = utils.camel_case_to_uscore(s).upper() self.name_lower = utils.camel_case_to_uscore(name_with_ns) + for m in self.methods: + m.calculate_c_names(strip_prefix, namespace) + + for s in self.signals: + s.calculate_c_names(strip_prefix, namespace) + + for p in self.properties: + p.calculate_c_names(strip_prefix, namespace) + #print '---' #print 'interface: ' + self.name #print 'camel_name: ' + self.camel_name @@ -230,17 +271,14 @@ class Interface: # name_upper: BAR_FROBNICATOR # name_lower: foo_bar_frobnicator - self.methods = [] - gdbus_methods = gi.get_methods() - for gm in gdbus_methods: - self.methods.append(Method(gm)) + #gdbus_methods = gi.get_methods() + #for gm in gdbus_methods: + # self.methods.append(Method(gm)) - self.signals = [] - gdbus_signals = gi.get_signals() - for gs in gdbus_signals: - self.signals.append(Signal(gs)) + #gdbus_signals = gi.get_signals() + #for gs in gdbus_signals: + # self.signals.append(Signal(gs)) - self.properties = [] - gdbus_properties = gi.get_properties() - for gp in gdbus_properties: - self.properties.append(Property(gp)) + #gdbus_properties = gi.get_properties() + #for gp in gdbus_properties: + # self.properties.append(Property(gp)) diff --git a/src/parser.py b/src/parser.py new file mode 100644 index 0000000..50fe810 --- /dev/null +++ b/src/parser.py @@ -0,0 +1,152 @@ +# -*- Mode: Python -*- + +import dbustypes + +import sys +import xml.parsers.expat + +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' + + def __init__(self, xml_data, dbus_strip_prefix, dbus_namespace): + self._parser = xml.parsers.expat.ParserCreate() + 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.dbus_strip_prefix = dbus_strip_prefix + self.dbus_namespace = dbus_namespace + self.state = DBusXMLParser.STATE_TOP + self.state_stack = [] + self._cur_object = None + self._cur_object_stack = [] + + self._parser.Parse(xml_data) + + 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_TOP: + if name == DBusXMLParser.STATE_NODE: + self.state = DBusXMLParser.STATE_NODE + else: + raise RuntimeError('Cannot go from state "%s" to element with name "%s"'%(self.state, name)) + elif self.state == DBusXMLParser.STATE_NODE: + if name == DBusXMLParser.STATE_INTERFACE: + self.state = DBusXMLParser.STATE_INTERFACE + iface = dbustypes.Interface(attrs['name']) + self._cur_object = iface + self.parsed_interfaces.append(iface) + elif name == DBusXMLParser.STATE_ANNOTATION: + self.state = DBusXMLParser.STATE_ANNOTATION + anno = dbustypes.Annotation(attrs['name'], attrs['value']) + self._cur_object.annotations.append(anno) + self._cur_object = anno + else: + raise RuntimeError('Cannot go from state "%s" to element with name "%s"'%(self.state, name)) + elif self.state == DBusXMLParser.STATE_INTERFACE: + if name == DBusXMLParser.STATE_METHOD: + self.state = DBusXMLParser.STATE_METHOD + method = dbustypes.Method(attrs['name']) + self._cur_object.methods.append(method) + self._cur_object = method + elif name == DBusXMLParser.STATE_SIGNAL: + self.state = DBusXMLParser.STATE_SIGNAL + signal = dbustypes.Signal(attrs['name']) + self._cur_object.signals.append(signal) + self._cur_object = signal + elif name == DBusXMLParser.STATE_PROPERTY: + self.state = DBusXMLParser.STATE_PROPERTY + prop = dbustypes.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 = dbustypes.Annotation(attrs['name'], attrs['value']) + self._cur_object.annotations.append(anno) + self._cur_object = anno + else: + raise RuntimeError('Cannot go from state "%s" to element with name "%s"'%(self.state, name)) + elif self.state == DBusXMLParser.STATE_METHOD: + if name == DBusXMLParser.STATE_ARG: + self.state = DBusXMLParser.STATE_ARG + arg = dbustypes.Arg(attrs['name'], attrs['type']) + direction = attrs['direction'] + if direction == 'in': + self._cur_object.in_args.append(arg) + elif direction == 'out': + self._cur_object.out_args.append(arg) + else: + raise RuntimeError('Invalid direction "%s"'%(direction)) + self._cur_object = arg + elif name == DBusXMLParser.STATE_ANNOTATION: + self.state = DBusXMLParser.STATE_ANNOTATION + anno = dbustypes.Annotation(attrs['name'], attrs['value']) + self._cur_object.annotations.append(anno) + self._cur_object = anno + else: + raise RuntimeError('Cannot go from state "%s" to element with name "%s"'%(self.state, name)) + elif self.state == DBusXMLParser.STATE_SIGNAL: + if name == DBusXMLParser.STATE_ARG: + self.state = DBusXMLParser.STATE_ARG + arg = dbustypes.Arg(attrs['name'], attrs['type']) + self._cur_object.args.append(arg) + self._cur_object = arg + elif name == DBusXMLParser.STATE_ANNOTATION: + self.state = DBusXMLParser.STATE_ANNOTATION + anno = dbustypes.Annotation(attrs['name'], attrs['value']) + self._cur_object.annotations.append(anno) + self._cur_object = anno + else: + raise RuntimeError('Cannot go from state "%s" to element with name "%s"'%(self.state, name)) + elif self.state == DBusXMLParser.STATE_PROPERTY: + if name == DBusXMLParser.STATE_ANNOTATION: + self.state = DBusXMLParser.STATE_ANNOTATION + anno = dbustypes.Annotation(attrs['name'], attrs['value']) + self._cur_object.annotations.append(anno) + self._cur_object = anno + else: + raise RuntimeError('Cannot go from state "%s" to element with name "%s"'%(self.state, name)) + elif self.state == DBusXMLParser.STATE_ARG: + if name == DBusXMLParser.STATE_ANNOTATION: + self.state = DBusXMLParser.STATE_ANNOTATION + anno = dbustypes.Annotation(attrs['name'], attrs['value']) + self._cur_object.annotations.append(anno) + self._cur_object = anno + else: + raise RuntimeError('Cannot go from state "%s" to element with name "%s"'%(self.state, name)) + elif self.state == DBusXMLParser.STATE_ANNOTATION: + if name == DBusXMLParser.STATE_ANNOTATION: + self.state = DBusXMLParser.STATE_ANNOTATION + anno = dbustypes.Annotation(attrs['name'], attrs['value']) + self._cur_object.annotations.append(anno) + self._cur_object = anno + else: + raise RuntimeError('Cannot go from state "%s" to element with name "%s"'%(self.state, name)) + else: + raise RuntimeError('Unhandled state "%s" while entering element with name "%s"'%(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, dbus_strip_prefix, dbus_namespace): + parser = DBusXMLParser(xml_data, dbus_strip_prefix, dbus_namespace) + return parser.parsed_interfaces |