diff options
Diffstat (limited to 'gi/overrides/GLib.py')
-rw-r--r-- | gi/overrides/GLib.py | 322 |
1 files changed, 230 insertions, 92 deletions
diff --git a/gi/overrides/GLib.py b/gi/overrides/GLib.py index 78d8c352..ac783be6 100644 --- a/gi/overrides/GLib.py +++ b/gi/overrides/GLib.py @@ -21,10 +21,21 @@ from ..importer import modules from .._gi import variant_new_tuple, variant_type_from_string -GLib = modules['GLib'].introspection_module +GLib = modules['GLib']._introspection_module __all__ = [] +def _create_variant(value): + '''Create a variant containing the variant "value". + + This is usually done with the GLib.Variant.new_variant() leaf + constructor, but this is currently broken, see GNOME#639952. + ''' + builder = GLib.VariantBuilder() + builder.init(variant_type_from_string('v')) + builder.add_value(value) + return builder.end() + class _VariantCreator(object): _LEAF_CONSTRUCTORS = { @@ -41,115 +52,242 @@ class _VariantCreator(object): 's': GLib.Variant.new_string, 'o': GLib.Variant.new_object_path, 'g': GLib.Variant.new_signature, - 'v': GLib.Variant.new_variant, + #'v': GLib.Variant.new_variant, + 'v': _create_variant, } - def __init__(self, format_string, args): - self._format_string = format_string - self._args = args + def _create(self, format, args): + '''Create a GVariant object from given format and argument list. - def create(self): - if self._format_string_is_leaf(): - return self._new_variant_leaf() + This method recursively calls itself for complex structures (arrays, + dictionaries, boxed). - format_char = self._pop_format_char() - arg = self._pop_arg() + Return a tuple (variant, rest_format, rest_args) with the generated + GVariant, the remainder of the format string, and the remainder of the + arguments. - if format_char == 'm': - raise NotImplementedError() - else: - builder = GLib.VariantBuilder() - if format_char == '(': - builder.init(variant_type_from_string('r')) - elif format_char == '{': - builder.init(variant_type_from_string('{?*}')) + If args is None, then this won't actually consume any arguments, and + just parse the format string and generate empty GVariant structures. + This is required for creating empty dictionaries or arrays. + ''' + # leaves (simple types) + constructor = self._LEAF_CONSTRUCTORS.get(format[0]) + if constructor: + if args is not None: + if not args: + raise TypeError('not enough arguments for GVariant format string') + v = constructor(args[0]) + return (constructor(args[0]), format[1:], args[1:]) else: - raise NotImplementedError() - format_char = self._pop_format_char() - while format_char not in [')', '}']: - builder.add_value(Variant(format_char, arg)) - format_char = self._pop_format_char() - if self._args: - arg = self._pop_arg() - return builder.end() - - def _format_string_is_leaf(self): - format_char = self._format_string[0] - return not format_char in ['m', '(', '{'] - - def _format_string_is_nnp(self): - format_char = self._format_string[0] - return format_char in ['a', 's', 'o', 'g', '^', '@', '*', '?', 'r', - 'v', '&'] - - def _new_variant_leaf(self): - if self._format_string_is_nnp(): - return self._new_variant_nnp() - - format_char = self._pop_format_char() - arg = self._pop_arg() - - return _VariantCreator._LEAF_CONSTRUCTORS[format_char](arg) - - def _new_variant_nnp(self): - format_char = self._pop_format_char() - arg = self._pop_arg() - - if format_char == '&': - format_char = self._pop_format_char() - - if format_char == 'a': - builder = GLib.VariantBuilder() - builder.init(variant_type_from_string('a*')) + return (None, format[1:], None) - element_format_string = self._pop_leaf_format_string() + if format[0] == '(': + return self._create_tuple(format, args) - if isinstance(arg, dict): - for element in arg.items(): - value = Variant(element_format_string, *element) - builder.add_value(value) - else: - for element in arg: - value = Variant(element_format_string, element) - builder.add_value(value) - return builder.end() - elif format_char == '^': - raise NotImplementedError() - elif format_char == '@': - raise NotImplementedError() - elif format_char == '*': - raise NotImplementedError() - elif format_char == 'r': - raise NotImplementedError() - elif format_char == '?': - raise NotImplementedError() + if format.startswith('a{'): + return self._create_dict(format, args) + + if format[0] == 'a': + return self._create_array(format, args) + + raise NotImplementedError('cannot handle GVariant type ' + format) + + def _create_tuple(self, format, args): + '''Handle the case where the outermost type of format is a tuple.''' + + format = format[1:] # eat the '(' + builder = GLib.VariantBuilder() + builder.init(variant_type_from_string('r')) + if args is not None: + if not args or type(args[0]) != type(()): + raise (TypeError, 'expected tuple argument') + + for i in range(len(args[0])): + if format.startswith(')'): + raise (TypeError, 'too many arguments for tuple signature') + + (v, format, _) = self._create(format, args[0][i:]) + builder.add_value(v) + args = args[1:] + return (builder.end(), format[1:], args) + + def _create_dict(self, format, args): + '''Handle the case where the outermost type of format is a dict.''' + + builder = GLib.VariantBuilder() + if args is None or not args[0]: + # empty value: we need to call _create() to parse the subtype, + # and specify the element type precisely + rest_format = self._create(format[2:], None)[1] + rest_format = self._create(rest_format, None)[1] + if not rest_format.startswith('}'): + raise ValueError('dictionary type string not closed with }') + rest_format = rest_format[1:] # eat the } + element_type = format[:len(format) - len(rest_format)] + builder.init(variant_type_from_string(element_type)) else: - return _VariantCreator._LEAF_CONSTRUCTORS[format_char](arg) + builder.init(variant_type_from_string('a{?*}')) + for k, v in args[0].items(): + (key_v, rest_format, _) = self._create(format[2:], [k]) + (val_v, rest_format, _) = self._create(rest_format, [v]) - def _pop_format_char(self): - format_char = self._format_string[0] - self._format_string = self._format_string[1:] - return format_char + if not rest_format.startswith('}'): + raise ValueError('dictionary type string not closed with }') + rest_format = rest_format[1:] # eat the } - def _pop_leaf_format_string(self): - # FIXME: This will break when the leaf is inside a tuple or dict entry - format_string = self._format_string - self._format_string = '' - return format_string + entry = GLib.VariantBuilder() + entry.init(variant_type_from_string('{?*}')) + entry.add_value(key_v) + entry.add_value(val_v) + builder.add_value(entry.end()) - def _pop_arg(self): - arg = self._args[0] - self._args = self._args[1:] - return arg + if args is not None: + args = args[1:] + return (builder.end(), rest_format, args) + + def _create_array(self, format, args): + '''Handle the case where the outermost type of format is an array.''' + + builder = GLib.VariantBuilder() + if args is None or not args[0]: + # empty value: we need to call _create() to parse the subtype, + # and specify the element type precisely + rest_format = self._create(format[1:], None)[1] + element_type = format[:len(format) - len(rest_format)] + builder.init(variant_type_from_string(element_type)) + else: + builder.init(variant_type_from_string('a*')) + for i in range(len(args[0])): + (v, rest_format, _) = self._create(format[1:], args[0][i:]) + builder.add_value(v) + if args is not None: + args = args[1:] + return (builder.end(), rest_format, args) class Variant(GLib.Variant): - def __new__(cls, format_string, *args): - creator = _VariantCreator(format_string, args) - return creator.create() + def __new__(cls, format_string, value): + '''Create a GVariant from a native Python object. + + format_string is a standard GVariant type signature, value is a Python + object whose structure has to match the signature. + + Examples: + GLib.Variant('i', 1) + GLib.Variant('(is)', (1, 'hello')) + GLib.Variant('(asa{sv})', ([], {'foo': GLib.Variant('b', True), + 'bar': GLib.Variant('i', 2)})) + ''' + creator = _VariantCreator() + (v, rest_format, _) = creator._create(format_string, [value]) + if rest_format: + raise TypeError('invalid remaining format string: "%s"' % rest_format) + return v def __repr__(self): return '<GLib.Variant(%s)>' % getattr(self, 'print')(True) + def unpack(self): + '''Decompose a GVariant into a native Python object.''' + + LEAF_ACCESSORS = { + 'b': self.get_boolean, + 'y': self.get_byte, + 'n': self.get_int16, + 'q': self.get_uint16, + 'i': self.get_int32, + 'u': self.get_uint32, + 'x': self.get_int64, + 't': self.get_uint64, + 'h': self.get_handle, + 'd': self.get_double, + 's': self.get_string, + 'o': self.get_string, # object path + 'g': self.get_string, # signature + } + + # simple values + la = LEAF_ACCESSORS.get(self.get_type_string()) + if la: + return la() + + # tuple + if self.get_type_string().startswith('('): + res = [self.get_child_value(i).unpack() + for i in range(self.n_children())] + return tuple(res) + + # dictionary + if self.get_type_string().startswith('a{'): + res = {} + for i in range(self.n_children()): + v = self.get_child_value(i) + res[v.get_child_value(0).unpack()] = v.get_child_value(1).unpack() + return res + + # array + if self.get_type_string().startswith('a'): + return [self.get_child_value(i).unpack() + for i in range(self.n_children())] + + # variant (just unbox transparently) + if self.get_type_string().startswith('v'): + return self.get_variant().unpack() + + raise NotImplementedError('unsupported GVariant type ' + self.get_type_string()) + + # + # Pythonic iterators + # + def __len__(self): + if self.get_type_string() in ['s', 'o', 'g']: + return len(self.get_string()) + if self.get_type_string().startswith('a') or self.get_type_string().startswith('('): + return self.n_children() + raise TypeError('GVariant type %s does not have a length' % self.get_type_string()) + + def __getitem__(self, key): + # dict + if self.get_type_string().startswith('a{'): + try: + val = self.lookup_value(key, variant_type_from_string('*')) + if val is None: + raise KeyError(key) + return val.unpack() + except TypeError: + # lookup_value() only works for string keys, which is certainly + # the common case; we have to do painful iteration for other + # key types + for i in range(self.n_children()): + v = self.get_child_value(i) + if v.get_child_value(0).unpack() == key: + return v.get_child_value(1).unpack() + raise KeyError(key) + + # array/tuple + if self.get_type_string().startswith('a') or self.get_type_string().startswith('('): + key = int(key) + if key < 0: + key = self.n_children() + key + if key < 0 or key >= self.n_children(): + raise IndexError('list index out of range') + return self.get_child_value(key).unpack() + + # string + if self.get_type_string() in ['s', 'o', 'g']: + return self.get_string().__getitem__(key) + + raise TypeError('GVariant type %s is not a container' % self.get_type_string()) + + def keys(self): + if not self.get_type_string().startswith('a{'): + return TypeError, 'GVariant type %s is not a dictionary' % self.get_type_string() + + res = [] + for i in range(self.n_children()): + v = self.get_child_value(i) + res.append(v.get_child_value(0).unpack()) + return res + @classmethod def new_tuple(cls, *elements): return variant_new_tuple(elements) |