File: GLib-Variant.lua

package info (click to toggle)
lua-lgi 0.9.2-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,388 kB
  • sloc: ansic: 5,082; makefile: 169; sh: 31
file content (327 lines) | stat: -rw-r--r-- 11,048 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
------------------------------------------------------------------------------
--
--  LGI GLib Variant support implementation.
--
--  Copyright (c) 2011 Pavel Holejsovsky
--  Licensed under the MIT license:
--  http://www.opensource.org/licenses/mit-license.php
--
------------------------------------------------------------------------------

local select, type, pairs, tostring, setmetatable, error, assert
   = select, type, pairs, tostring, setmetatable, error, assert

local lgi = require 'lgi'
local core = require 'lgi.core'
local bytes = require 'bytes'
local gi = core.gi
local GLib = lgi.GLib

local Variant = GLib.Variant
local variant_info = gi.GLib.Variant

-- Add custom methods for variant handling.
Variant._refsink = variant_info.methods.ref_sink
Variant._free = variant_info.methods.unref

-- Add 'type' property to variant, an alias to get_type().
Variant._attribute = { type = { get = Variant.get_type_string } }

local VariantBuilder = GLib.VariantBuilder
local VariantType = GLib.VariantType

-- VariantBuilder and VariantType are boxed only in glib-2.29 and
-- newer, add custom _free recipe for older glibs.
if not VariantBuilder._gtype then
   VariantBuilder._free = gi.GLib.VariantBuilder.methods.unref
end
if not VariantType._gtype then
   VariantBuilder._free = gi.GLib.VariantType.methods.free
end

-- Add constants containing basic variant types.
for k, v in pairs {
   BOOLEAN = 'b', BYTE = 'y', INT16 = 'n', UINT16 = 'q',
   INT32 = 'i', UINT32 = 'u', INT64 = 'x', UINT64 = 't',
   DOUBLE = 'd', STRING = 's', OBJECT_PATH = 'o', SIGNATURE = 'g',
   VARIANT = 'v', ANY = '*', BASIC = '?', MAYBE = 'm*', ARRAY = 'a*',
   TUPLE = 'r', UNIT = '()', DICT_ENTRY = '{?*}', DICTIONARY = 'a{?*}',
   STRING_ARRAY = 'as', BYTESTRING = 'ay',  BYTESTRING_ARRAY = 'aay',
} do VariantType[k] = VariantType.new(v) end

-- g_variant_get_type() is hidden by g-i (because scanner thinks that
-- this is GType getter), so provide manual override.
function Variant:get_type()
   return VariantType.new(Variant.get_type_string(self))
end

-- Implementation of Variant.new() from type and value.
local variant_new

local variant_basic_typemap = {
   b = 'boolean', y = 'byte', n = 'int16', q = 'uint16',
   i = 'int32', u = 'uint32', x = 'int64', t = 'uint64',
   d = 'double', s = 'string', o = 'object_path', g = 'signature', }

-- Checks validity of variant type format beginning at pos, return
-- position in format string after end of valid part.  Returns nil
-- when format is not valid.
local function read_format(format, pos, basic)
   local t = format:sub(pos, pos)
   pos = pos + 1
   if variant_basic_typemap[t] then
      return pos
   elseif basic then
      return nil
   elseif t =='v' then
      return pos
   elseif t == 'a' or t == 'm' then
      return read_format(format, pos)
   elseif t == '{' then
      pos = read_format(format, pos, true)
      if pos then pos = read_format(format, pos) end
      if not pos or format:sub(pos, pos) ~= '}' then return nil end
      return pos + 1
   elseif t == '(' then
      while format:sub(pos, pos) ~=  ')' do
	 pos = read_format(format, pos)
	 if not pos then return nil end
      end
      return pos + 1
   end
end

local function variant_new_basic(format, val)
   local func = variant_basic_typemap[format]
   if not func then return end
   local v = Variant['new_' .. func](val)
   if not v then
      error(("Variant.new(`%s') - invalid source value"):format(format))
   end
   return v
end

function variant_new(format, pos, val)
   local t = format:sub(pos, pos)
   pos = pos + 1
   local variant  = variant_new_basic(t, val)
   if variant then
      return variant, pos
   elseif t == 'v' then
      return Variant.new_variant(val), pos
   elseif t == 'm' then
      local epos
      if val then
	 variant, epos = variant_new(format, pos, val)
      else
	 epos = read_format(format, pos)
	 if not epos then return nil end
      end
      return Variant.new_maybe(VariantType.new(format:sub(pos, epos - 1)),
			       variant), epos
   elseif t == 'a' then
      if format:sub(pos, pos) == 'y' then
	 -- Bytestring is just simple Lua string.
	 return Variant.new_bytestring(val), pos + 1
      end
      local epos = read_format(format, pos)
      if not epos then return nil end
      local et = VariantType.new(format:sub(pos, epos - 1))
      local builder = VariantBuilder.new(VariantType.new_array(et))
      if et:is_subtype_of(VariantType.DICT_ENTRY) then
	 -- Map dictionary to Lua table directly.
	 for k, v in pairs(val) do
	    builder:add_value(Variant.new_dict_entry(
				 variant_new(format, pos + 1, k),
				 variant_new(format, pos + 2, v)))
	 end
      else
	 -- We have an issue with 'array with holes'. An attempt is
	 -- made here to work around it with 'n' field, if present.
	 for i = 1, val.n or #val do
	    builder:add_value(variant_new(format, pos, val[i]))
	 end
      end
      return builder:_end(), epos
   elseif t == '(' or t == '{' then
      -- Extract and check whole tuple or entry format.
      local epos = read_format(format, pos -1)
      if not epos then return nil end

      -- Prepare builder with specified format.
      local builder = VariantBuilder.new(
	 VariantType.new(format:sub(pos - 1, epos - 1)))

      -- Loop through provided value array and build variant using
      -- prepared builder.
      local i = 1
      while not format:sub(pos, pos):match('^[%)}]') do
	 local v, epos = variant_new(format, pos, val[i])
	 if not v then return nil end
	 builder:add_value(v)
	 pos = epos
	 i = i + 1
      end
      return builder:_end(), pos + 1
   end
end

-- Variant.new() is just a facade over variant_new backend.
function Variant.new(vt, val)
   if type(vt) == 'userdata' then
      -- Wrap existing pointer to variant.
      return core.record.new(Variant, vt, val)
   end
   if type(vt) ~= 'string' then vt = vt:dup_string() end
   local v, epos = variant_new(vt, 1, val)
   if not v or epos ~= #vt + 1 then
      error(("Variant.new(`%s') - invalid type"):format(vt))
   end
   return v
end
function Variant:_new(...) return Variant.new(...) end

-- Implement VariantBuilder:add() using the same facade.
function VariantBuilder:add(type, val)
   VariantBuilder.add_value(Variant.new(type, val))
end

-- Converts variant to nearest possible Lua value, but leaves arrays
-- intact (use indexing and/or iterators for handling arrays).
local simple_unpack_map = {
   b = 'boolean', y = 'byte', n = 'int16', q = 'uint16',
   i = 'int32', u = 'uint32', x = 'int64', t = 'uint64',
   d = 'double', s = 'string', o = 'string', g = 'string', v = 'variant'
}
local function variant_get(v)
   local type = v:get_type_string()
   local func = simple_unpack_map[type]
   if func then
      return Variant['get_' .. func](v)
   elseif type:match('^m') then
      return v:n_children() == 1 and variant_get(v:get_child_value(0)) or nil
   elseif type:match('^[{(r]') then
      -- Unpack dictionary entry or tuple into array.
      local array = { n = v:n_children() }
      for i = 1, array.n do
	 array[i] = variant_get(v:get_child_value(i - 1))
      end
      return array
   elseif Variant.is_of_type(v, VariantType.BYTESTRING) then
      return tostring(Variant.get_bytestring(v))
   elseif Variant.is_of_type(v, VariantType.DICTIONARY) then
      -- Return proxy table which dynamically looks up items in the
      -- target variant.
      local meta = {}
      if Variant.is_of_type(v, VariantType.new('a{s*}')) then
	 -- Use g_variant_lookup_value.
	 function meta:__index(key)
	    local found = Variant.lookup_value(v, key)
	    return found and variant_get(found)
	 end
      else
	 -- Custom search, walk key-by-key.  Cache key positions in
	 -- the meta table.
	 function meta:__index(key)
	    for i = 0, Variant.n_children(v) - 1 do
	       local entry = Variant.get_child_value(v, i)
	       local vkey = variant_get(Variant.get_child_value(entry, 0))
	       if vkey == key then
		  local found = Variant.get_child_value(entry, 1)
		  return found and variant_get(found)
	       end
	    end
	 end
      end
      return setmetatable({}, meta)
   end

   -- No simple unpacking is possible, return just self.  Complex
   -- compound types are meant to be accessed by indexing or
   -- iteration, implemented below.
   return v
end

-- Map simple unpacking to reading 'value' property.
Variant._attribute.value = { get = variant_get }

-- Define meaning of # and number-indexing to children access. Note
-- that GVariant g_asserts when these methods are invoked on variants
-- of inappropriate type, so we have to check manually before.
function Variant:_len()
   return self:is_container() and self:n_children() or 0
end

local variant_element = Variant._element
function Variant:_element(variant, name)
   -- If number is requested, consider it a special operation,
   -- indexing a variant.
   if type(name) == 'number' then return name, '_index' end
   return variant_element(self, variant, name)
end

function Variant:_access_index(variant, index, ...)
   assert(select('#', ...) == 0, 'GLib.Variant is not writable')
   if (Variant.is_container(variant) and
       Variant.n_children(variant) >= index) then
      return Variant.get_child_value(variant, index - 1).value
   end
end

-- Implementation of iterators over compound variant (simulating
-- standard Lua pairs() and ipairs() methods).
local function variant_inext(parent, index)
   index = index + 1
   if index <= #parent then
      return index, parent[index]
   end
end

function Variant:ipairs()
   return variant_inext, self, 0
end

function Variant:pairs()
   if self:is_of_type(VariantType.DICTIONARY) then
      -- For dictionaries, provide closure iterator which goes through
      -- all key-value pairs of the parent.
      local index = 0
      return function()
		index = index + 1
		if index <= #self then
		   local child = self[index]
		   return child[1], child[2]
		end
	     end
   end

   -- For non-dictionaries, pairs() is the same as ipairs().
   return self:ipairs()
end

-- Serialization support.  Override Variant:get_data() with safer
-- method which fills size to the resulting ref.
Variant._attribute.data = {}
function Variant._attribute.data:get()
   local buffer = bytes.new(Variant.get_size(self))
   Variant.store(self, buffer)
   return buffer
end

-- Map get_data to read-only 'data' property.

-- Override for new_from_data.  Takes care mainly about tricky
-- DestroyNotify handling.
local variant_new_from_data = Variant.new_from_data
function Variant.new_from_data(vt, data, trusted)
   if type(vt) == 'string' then vt = VariantType.new(vt) end
   if trusted == nil then trusted = true end
   return variant_new_from_data(
      vt, data, trusted,
      -- DestroyNotify implemented as closure which holds 'data' value
      -- as an upvalue.  The 'notify' argument is scope-async, which
      -- means that closure together with its upvalue will be
      -- destroyed after called.  Up to that time 'data' is safely
      -- held in upvalue for this closure.
      function() data = nil end)
end