summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Zeuthen <davidz@redhat.com>2011-02-14 07:05:32 -0500
committerDavid Zeuthen <davidz@redhat.com>2011-02-14 07:05:32 -0500
commit55f2c2cbc44b45150be7d1d8a257b5823ebba902 (patch)
tree7dc21346cb8041f927ae5b2df463b7422b31587f
parentfc056dfd5dbd87aa40ea20e707d03d8e50600198 (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.am3
-rw-r--r--src/codegen.py96
-rw-r--r--src/dbustypes.py134
-rw-r--r--src/parser.py152
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