ruby-dbus 0.18.0.beta2 → 0.18.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c89eebb575c67de57670fdc2e8b70b4c08dcaf7fc3c1bad858f1d23869571c1
4
- data.tar.gz: 4a4793ed412ebd25a636bed4dafa5367800658eb5d52d853ef1b2c0df0e3e272
3
+ metadata.gz: f8b0c43d3f27f0877e52c0adfb8014374829f97bcd38be6b9fabda1d5da8413c
4
+ data.tar.gz: 000eadf163b0fab2ef4ab39de81a79714b34392cd81b3be68e12587ddfa93372
5
5
  SHA512:
6
- metadata.gz: c03aa469aebb197943266b9147fba6b55383a22248e0814b0c2f7799c2e95d43b555f80a919c236a437c90805cbd7647498c1f8b1cf901d4a298da0b2d81e50c
7
- data.tar.gz: f7906b8c2308909cb8974240f3d8de3bc3e10a481fc15c91acd7af5526f405fd314df8b1a424683009a64dfc52ebfa9fa2c73497f8215cb403d80ab02866d56a
6
+ metadata.gz: c2206dcd935fed4e3711b88b2c0c265e5a335700f9137d2c3972f419791d9c25198d0acf9ba7aaecdbc6a25e540005c64321e5165c6c644838fb3557355861ac
7
+ data.tar.gz: 212cb8bdcd75e3f35622843f262cdb80eaa07c1919a7d17ffdf601e05c627556343b7855a014f60a220c8a757c201431d66799a6025a98d084aef6cc12f7d8bb
data/NEWS.md CHANGED
@@ -2,6 +2,41 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Ruby D-Bus 0.18.0.beta5 - 2022-04-27
6
+
7
+ API:
8
+ * DBus::Type instances are frozen.
9
+ * Data::Container classes (Array, Struct, DictEntry, but not Variant)
10
+ constructors (#initialize, .from_items, .from_typed) changed to have
11
+ a *type* argument instead of *member_type* or *member_types*.
12
+ * Added type factories
13
+ * Type::Array[type]
14
+ * Type::Hash[key_type, value_type]
15
+ * Type::Struct[type1, type2...]
16
+
17
+ Bug fixes:
18
+ * Properties containing Variants would return them doubly wrapped ([#111][]).
19
+
20
+ [#111]: https://github.com/mvidner/ruby-dbus/pull/111
21
+
22
+ ## Ruby D-Bus 0.18.0.beta4 - 2022-04-21
23
+
24
+ Bug fixes:
25
+ * Service-side properties: Fix Properties.Get, Properties.GetAll for
26
+ properties that contain arrays, on other than outermost level ([#109][]).
27
+ * Sending variants: fixed make_variant to correctly guess the signature
28
+ for UInt64 and number-keyed hashes/dictionaries.
29
+
30
+ [#109]: https://github.com/mvidner/ruby-dbus/pull/109
31
+
32
+ ## Ruby D-Bus 0.18.0.beta3 - 2022-04-10
33
+
34
+ Bug fixes:
35
+ * Service-side properties: Fix Properties.Get, Properties.GetAll for Array,
36
+ Dict, and Variant types ([#105][]).
37
+
38
+ [#105]: https://github.com/mvidner/ruby-dbus/pull/105
39
+
5
40
  ## Ruby D-Bus 0.18.0.beta2 - 2022-04-04
6
41
 
7
42
  API:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.18.0.beta2
1
+ 0.18.0.beta5
data/lib/dbus/data.rb CHANGED
@@ -43,7 +43,7 @@ module DBus
43
43
  data_class = Data::BY_TYPE_CODE[type.sigtype]
44
44
  # not nil because DBus.type validates
45
45
 
46
- data_class.from_typed(value, member_types: type.members)
46
+ data_class.from_typed(value, type: type)
47
47
  end
48
48
  module_function :make_typed
49
49
 
@@ -67,6 +67,15 @@ module DBus
67
67
  # for the specific see {Variant#member_type}
68
68
  # @return [Type] the exact type of this value
69
69
 
70
+ # @!method self.from_typed(value, type:)
71
+ # @param value [::Object]
72
+ # @param type [Type]
73
+ # @return [Base]
74
+ # @api private
75
+ # Use {Data.make_typed} instead.
76
+ # Construct an instance of the specific subclass, with a type further
77
+ # specified in the *type* argument.
78
+
70
79
  # Child classes must validate *value*.
71
80
  def initialize(value)
72
81
  @value = value
@@ -83,6 +92,11 @@ module DBus
83
92
  # Hash key equality
84
93
  # See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F
85
94
  alias eql? ==
95
+
96
+ # @param type [Type]
97
+ def self.assert_type_matches_class(type)
98
+ raise ArgumentError unless type.sigtype == type_code
99
+ end
86
100
  end
87
101
 
88
102
  # A value that is not a {Container}.
@@ -103,10 +117,10 @@ module DBus
103
117
  end
104
118
 
105
119
  # @param value [::Object]
106
- # @param member_types [::Array<Type>] (ignored, will be empty)
120
+ # @param type [Type]
107
121
  # @return [Basic]
108
- def self.from_typed(value, member_types:) # rubocop:disable Lint/UnusedMethodArgument
109
- # assert member_types.empty?
122
+ def self.from_typed(value, type:)
123
+ assert_type_matches_class(type)
110
124
  new(value)
111
125
  end
112
126
  end
@@ -132,39 +146,14 @@ module DBus
132
146
  end
133
147
  end
134
148
 
135
- # {DBus::Data::String}, {DBus::Data::ObjectPath}, or {DBus::Data::Signature}.
136
- class StringLike < Basic
137
- def self.fixed?
138
- false
139
- end
140
-
141
- def initialize(value)
142
- if value.is_a?(self.class)
143
- value = value.value
144
- else
145
- self.class.validate_raw!(value)
146
- end
147
-
148
- super(value)
149
- end
150
- end
151
-
152
- # Contains one or more other values.
153
- class Container < Base
154
- def self.basic?
155
- false
156
- end
157
-
158
- def self.fixed?
159
- false
160
- end
161
- end
162
-
163
149
  # Format strings for String#unpack, both little- and big-endian.
164
150
  Format = ::Struct.new(:little, :big)
165
151
 
166
152
  # Represents integers
167
153
  class Int < Fixed
154
+ # @!method self.range
155
+ # @return [Range] the full range of allowed values
156
+
168
157
  # @param value [::Integer,DBus::Data::Int]
169
158
  # @raise RangeError
170
159
  def initialize(value)
@@ -174,10 +163,6 @@ module DBus
174
163
 
175
164
  super(value)
176
165
  end
177
-
178
- def self.range
179
- raise NotImplementedError, "Abstract"
180
- end
181
166
  end
182
167
 
183
168
  # Byte.
@@ -399,6 +384,23 @@ module DBus
399
384
  end
400
385
  end
401
386
 
387
+ # {DBus::Data::String}, {DBus::Data::ObjectPath}, or {DBus::Data::Signature}.
388
+ class StringLike < Basic
389
+ def self.fixed?
390
+ false
391
+ end
392
+
393
+ def initialize(value)
394
+ if value.is_a?(self.class)
395
+ value = value.value
396
+ else
397
+ self.class.validate_raw!(value)
398
+ end
399
+
400
+ super(value)
401
+ end
402
+ end
403
+
402
404
  # UTF-8 encoded string.
403
405
  class String < StringLike
404
406
  def self.type_code
@@ -495,6 +497,21 @@ module DBus
495
497
  end
496
498
  end
497
499
 
500
+ # Contains one or more other values.
501
+ class Container < Base
502
+ def self.basic?
503
+ false
504
+ end
505
+
506
+ def self.fixed?
507
+ false
508
+ end
509
+
510
+ # For containers, the type varies among instances
511
+ # @see Base#type
512
+ attr_reader :type
513
+ end
514
+
498
515
  # An Array, or a Dictionary (Hash).
499
516
  class Array < Container
500
517
  def self.type_code
@@ -505,44 +522,32 @@ module DBus
505
522
  4
506
523
  end
507
524
 
508
- # @return [Type]
509
- attr_reader :member_type
510
-
511
- def type
512
- return @type if @type
513
-
514
- # TODO: reconstructing the type is cumbersome; have #initialize take *type* instead?
515
- # TODO: or rather add Type::Array[t]
516
- @type = Type.new("a")
517
- @type << member_type
518
- @type
519
- end
520
-
521
525
  # TODO: check that Hash keys are basic types
522
526
  # @param mode [:plain,:exact]
523
- # @param member_type [Type]
527
+ # @param type [Type]
524
528
  # @param hash [Boolean] are we unmarshalling an ARRAY of DICT_ENTRY
525
529
  # @return [Data::Array]
526
- def self.from_items(value, mode:, member_type:, hash: false)
530
+ def self.from_items(value, mode:, type:, hash: false)
527
531
  value = Hash[value] if hash
528
532
  return value if mode == :plain
529
533
 
530
- new(value, member_type: member_type)
534
+ new(value, type: type)
531
535
  end
532
536
 
533
537
  # @param value [::Object]
534
- # @param member_types [::Array<Type>]
538
+ # @param type [Type]
535
539
  # @return [Data::Array]
536
- def self.from_typed(value, member_types:)
540
+ def self.from_typed(value, type:)
541
+ assert_type_matches_class(type)
537
542
  # TODO: validation
538
- member_type = member_types.first
543
+ member_type = type.child
539
544
 
540
545
  # TODO: Dict??
541
546
  items = value.map do |i|
542
547
  Data.make_typed(member_type, i)
543
548
  end
544
549
 
545
- new(items) # initialize(::Array<Data::Base>)
550
+ new(items, type: type) # initialize(::Array<Data::Base>)
546
551
  end
547
552
 
548
553
  # FIXME: should Data::Array be mutable?
@@ -551,12 +556,12 @@ module DBus
551
556
  # TODO: specify type or guess type?
552
557
  # Data is the exact type, so its constructor should be exact
553
558
  # and guesswork should be clearly labeled
554
- # @param member_type [SingleCompleteType,Type]
555
- def initialize(value, member_type:)
556
- member_type = DBus.type(member_type) unless member_type.is_a?(Type)
559
+ # @param type [SingleCompleteType,Type]
560
+ def initialize(value, type:)
561
+ type = DBus.type(type) unless type.is_a?(Type)
562
+ self.class.assert_type_matches_class(type)
563
+ @type = type
557
564
  # TODO: copy from another Data::Array
558
- @member_type = member_type
559
- @type = nil
560
565
  super(value)
561
566
  end
562
567
  end
@@ -573,48 +578,32 @@ module DBus
573
578
  8
574
579
  end
575
580
 
576
- # @return [::Array<Type>]
577
- attr_reader :member_types
578
-
579
- def type
580
- return @type if @type
581
-
582
- # TODO: reconstructing the type is cumbersome; have #initialize take *type* instead?
583
- # TODO: or rather add Type::Struct[t1, t2, ...]
584
- @type = Type.new(self.class.type_code, abstract: true)
585
- @member_types.each do |member_type|
586
- @type << member_type
587
- end
588
- @type
589
- end
590
-
591
581
  # @param value [::Array]
592
- def self.from_items(value, mode:, member_types:)
582
+ def self.from_items(value, mode:, type:)
593
583
  value.freeze
594
584
  return value if mode == :plain
595
585
 
596
- new(value, member_types: member_types)
586
+ new(value, type: type)
597
587
  end
598
588
 
599
589
  # @param value [::Object] (#size, #each)
600
- # @param member_types [::Array<Type>]
590
+ # @param type [Type]
601
591
  # @return [Struct]
602
- def self.from_typed(value, member_types:)
592
+ def self.from_typed(value, type:)
603
593
  # TODO: validation
594
+ member_types = type.members
604
595
  raise unless value.size == member_types.size
605
596
 
606
- @member_types = member_types
607
-
608
597
  items = member_types.zip(value).map do |item_type, item|
609
598
  Data.make_typed(item_type, item)
610
599
  end
611
600
 
612
- new(items, member_types: member_types) # initialize(::Array<Data::Base>)
601
+ new(items, type: type) # initialize(::Array<Data::Base>)
613
602
  end
614
603
 
615
- def initialize(value, member_types:)
616
- @member_types = member_types
617
- @type = nil
604
+ def initialize(value, type:)
605
+ self.class.assert_type_matches_class(type)
606
+ @type = type
618
607
  super(value)
619
608
  end
620
609
  end
@@ -637,20 +626,26 @@ module DBus
637
626
  end
638
627
 
639
628
  # @param value [::Object]
640
- # @param member_types [::Array<Type>]
629
+ # @param type [Type]
641
630
  # @return [Variant]
642
- def self.from_typed(value, member_types:) # rubocop:disable Lint/UnusedMethodArgument
643
- # assert member_types.empty?
631
+ def self.from_typed(value, type:)
632
+ assert_type_matches_class(type)
644
633
 
645
634
  # decide on type of value
646
- new(value)
635
+ new(value, member_type: nil)
647
636
  end
648
637
 
649
- # Note that for Variants type=="v",
638
+ # @return [Type]
639
+ def self.type
640
+ # memoize
641
+ @type ||= Type.new(type_code).freeze
642
+ end
643
+
644
+ # Note that for Variants type.to_s=="v",
650
645
  # for the specific see {Variant#member_type}
651
646
  # @return [Type] the exact type of this value
652
647
  def type
653
- "v"
648
+ self.class.type
654
649
  end
655
650
 
656
651
  # @return [Type]
@@ -687,30 +682,34 @@ module DBus
687
682
  8
688
683
  end
689
684
 
690
- # @return [::Array<Type>]
691
- attr_reader :member_types
692
-
693
- def type
694
- return @type if @type
695
-
696
- # TODO: reconstructing the type is cumbersome; have #initialize take *type* instead?
697
- @type = Type.new(self.class.type_code, abstract: true)
698
- @member_types.each do |member_type|
699
- @type << member_type
700
- end
701
- @type
702
- end
703
-
704
685
  # @param value [::Array]
705
- def self.from_items(value, mode:, member_types:) # rubocop:disable Lint/UnusedMethodArgument
686
+ def self.from_items(value, mode:, type:) # rubocop:disable Lint/UnusedMethodArgument
706
687
  value.freeze
707
688
  # DictEntry ignores the :exact mode
708
689
  value
709
690
  end
710
691
 
711
- def initialize(value, member_types:)
712
- @member_types = member_types
713
- @type = nil
692
+ # @param value [::Object] (#size, #each)
693
+ # @param type [Type]
694
+ # @return [DictEntry]
695
+ def self.from_typed(value, type:)
696
+ assert_type_matches_class(type)
697
+ member_types = type.members
698
+ # assert member_types.size == 2
699
+ # TODO: duplicated from Struct. Inherit/delegate?
700
+ # TODO: validation
701
+ raise unless value.size == member_types.size
702
+
703
+ items = member_types.zip(value).map do |item_type, item|
704
+ Data.make_typed(item_type, item)
705
+ end
706
+
707
+ new(items, type: type) # initialize(::Array<Data::Base>)
708
+ end
709
+
710
+ def initialize(value, type:)
711
+ self.class.assert_type_matches_class(type)
712
+ @type = type
714
713
  super(value)
715
714
  end
716
715
  end
data/lib/dbus/marshall.rb CHANGED
@@ -102,6 +102,8 @@ module DBus
102
102
  packet = data_class.from_raw(value, mode: mode)
103
103
  elsif data_class.basic?
104
104
  size = aligned_read_value(data_class.size_class)
105
+ # @raw_msg.align(data_class.alignment)
106
+ # ^ is not necessary because we've just read a suitably-aligned *size*
105
107
  value = @raw_msg.read(size)
106
108
  nul = @raw_msg.read(1)
107
109
  if nul != "\u0000"
@@ -116,7 +118,7 @@ module DBus
116
118
  values = signature.members.map do |child_sig|
117
119
  do_parse(child_sig, mode: mode)
118
120
  end
119
- packet = data_class.from_items(values, mode: mode, member_types: signature.members)
121
+ packet = data_class.from_items(values, mode: mode, type: signature)
120
122
 
121
123
  when Type::VARIANT
122
124
  data_sig = do_parse(Data::Signature.type, mode: :exact) # -> Data::Signature
@@ -145,7 +147,7 @@ module DBus
145
147
  items << item
146
148
  end
147
149
  is_hash = signature.child.sigtype == Type::DICT_ENTRY
148
- packet = data_class.from_items(items, mode: mode, member_type: signature.child, hash: is_hash)
150
+ packet = data_class.from_items(items, mode: mode, type: signature, hash: is_hash)
149
151
  end
150
152
  end
151
153
  packet
@@ -248,9 +250,10 @@ module DBus
248
250
  when Type::VARIANT
249
251
  append_variant(val)
250
252
  when Type::ARRAY
253
+ val = val.value if val.is_a?(Data::Array)
251
254
  append_array(type.child, val)
252
255
  when Type::STRUCT, Type::DICT_ENTRY
253
- val = val.value if val.is_a?(Data::Struct)
256
+ val = val.value if val.is_a?(Data::Struct) || val.is_a?(Data::DictEntry)
254
257
  unless val.is_a?(Array) || val.is_a?(Struct)
255
258
  type_name = Type::TYPE_MAPPING[type.sigtype].first
256
259
  raise TypeException, "#{type_name} expects an Array or Struct, seen #{val.class}"
@@ -279,8 +282,11 @@ module DBus
279
282
 
280
283
  def append_variant(val)
281
284
  vartype = nil
282
- if val.is_a?(DBus::Data::Base)
283
- vartype = val.type # FIXME: box or unbox another variant?
285
+ if val.is_a?(DBus::Data::Variant)
286
+ vartype = val.member_type
287
+ vardata = val.value
288
+ elsif val.is_a?(DBus::Data::Base)
289
+ vartype = val.type
284
290
  vardata = val.value
285
291
  elsif val.is_a?(Array) && val.size == 2
286
292
  case val[0]
@@ -351,15 +357,23 @@ module DBus
351
357
  elsif value.is_a? Hash
352
358
  h = {}
353
359
  value.each_key { |k| h[k] = make_variant(value[k]) }
354
- ["a{sv}", h]
360
+ key_type = if value.empty?
361
+ "s"
362
+ else
363
+ t, = make_variant(value.first.first)
364
+ t
365
+ end
366
+ ["a{#{key_type}v}", h]
355
367
  elsif value.respond_to? :to_str
356
368
  ["s", value.to_str]
357
369
  elsif value.respond_to? :to_int
358
370
  i = value.to_int
359
- if (-2_147_483_648...2_147_483_648).cover?(i)
371
+ if Data::Int32.range.cover?(i)
360
372
  ["i", i]
361
- else
373
+ elsif Data::Int64.range.cover?(i)
362
374
  ["x", i]
375
+ else
376
+ ["t", i]
363
377
  end
364
378
  end
365
379
  end
data/lib/dbus/type.rb CHANGED
@@ -29,14 +29,12 @@ module DBus
29
29
  # For documentation purposes only.
30
30
  class Prototype < String; end
31
31
 
32
- # = D-Bus type module
33
- #
34
- # This module containts the constants of the types specified in the D-Bus
35
- # protocol.
32
+ # Represents the D-Bus types.
36
33
  #
37
34
  # Corresponds to {SingleCompleteType}.
35
+ # Instances are immutable/frozen once fully constructed.
38
36
  #
39
- # See also {DBus::Data::Signature}
37
+ # See also {DBus::Data::Signature} which is "type on the wire".
40
38
  class Type
41
39
  # Mapping from type number to name and alignment.
42
40
  TYPE_MAPPING = {
@@ -104,8 +102,9 @@ module DBus
104
102
  end
105
103
  end
106
104
 
107
- @sigtype = sigtype
108
- @members = []
105
+ @sigtype = sigtype.freeze
106
+ @members = [] # not frozen yet, Parser#parse_one or Factory will do it
107
+ freeze
109
108
  end
110
109
 
111
110
  # Return the required alignment for the type.
@@ -124,16 +123,15 @@ module DBus
124
123
  when DICT_ENTRY
125
124
  "{#{@members.collect(&:to_s).join}}"
126
125
  else
127
- if !TYPE_MAPPING.keys.member?(@sigtype)
128
- raise NotImplementedError
129
- end
130
-
131
126
  @sigtype.chr
132
127
  end
133
128
  end
134
129
 
135
130
  # Add a new member type _item_.
131
+ # @param item [Type]
136
132
  def <<(item)
133
+ raise ArgumentError unless item.is_a?(Type)
134
+
137
135
  if ![STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
138
136
  raise SignatureException
139
137
  end
@@ -232,6 +230,7 @@ module DBus
232
230
  else
233
231
  res = Type.new(char)
234
232
  end
233
+ res.members.freeze
235
234
  res
236
235
  end
237
236
 
@@ -243,7 +242,7 @@ module DBus
243
242
  while (c = nextchar)
244
243
  ret << parse_one(c)
245
244
  end
246
- ret
245
+ ret.freeze
247
246
  end
248
247
 
249
248
  # Parse one {SingleCompleteType}
@@ -255,9 +254,116 @@ module DBus
255
254
  t = parse_one(c)
256
255
  raise SignatureException, "Has more than a Single Complete Type: #{@signature}" unless nextchar.nil?
257
256
 
257
+ t.freeze
258
+ end
259
+ end
260
+
261
+ class Factory
262
+ # @param type [Type,SingleCompleteType,Class]
263
+ # @see from_plain_class
264
+ # @return [Type] (frozen)
265
+ def self.make_type(type)
266
+ case type
267
+ when Type
268
+ type
269
+ when String
270
+ DBus.type(type)
271
+ when Class
272
+ from_plain_class(type)
273
+ else
274
+ msg = "Expecting DBus::Type, DBus::SingleCompleteType(aka ::String), or Class, got #{type.inspect}"
275
+ raise ArgumentError, msg
276
+ end
277
+ end
278
+
279
+ # Make a {Type} corresponding to some plain classes:
280
+ # - String
281
+ # - Float
282
+ # - DBus::ObjectPath
283
+ # - DBus::Signature, DBus::SingleCompleteType
284
+ # @param klass [Class]
285
+ # @return [Type] (frozen)
286
+ def self.from_plain_class(klass)
287
+ @signature_type ||= DBus.type(SIGNATURE)
288
+ @class_to_type ||= {
289
+ DBus::ObjectPath => DBus.type(OBJECT_PATH),
290
+ DBus::Signature => @signature_type,
291
+ DBus::SingleCompleteType => @signature_type,
292
+ String => DBus.type(STRING),
293
+ Float => DBus.type(DOUBLE)
294
+ }
295
+ t = @class_to_type[klass]
296
+ raise ArgumentError, "Cannot convert plain class #{klass} to a D-Bus type" if t.nil?
297
+
298
+ t
299
+ end
300
+ end
301
+
302
+ # Syntactic helper for constructing an array Type.
303
+ # You may be looking for {Data::Array} instead.
304
+ # @example
305
+ # t = Type::Array[Type::INT16]
306
+ class ArrayFactory < Factory
307
+ # @param member_type [Type,SingleCompleteType]
308
+ # @return [Type] (frozen)
309
+ def self.[](member_type)
310
+ t = Type.new(ARRAY)
311
+ t << make_type(member_type)
312
+ t.members.freeze
313
+ t
314
+ end
315
+ end
316
+
317
+ # @example
318
+ # t = Type::Array[Type::INT16]
319
+ Array = ArrayFactory
320
+
321
+ # Syntactic helper for constructing a hash Type.
322
+ # You may be looking for {Data::Array} and {Data::DictEntry} instead.
323
+ # @example
324
+ # t = Type::Hash[Type::STRING, Type::VARIANT]
325
+ class HashFactory < Factory
326
+ # @param key_type [Type,SingleCompleteType]
327
+ # @param value_type [Type,SingleCompleteType]
328
+ # @return [Type] (frozen)
329
+ def self.[](key_type, value_type)
330
+ t = Type.new(ARRAY)
331
+ de = Type.new(DICT_ENTRY, abstract: true)
332
+ de << make_type(key_type)
333
+ de << make_type(value_type)
334
+ de.members.freeze
335
+ t << de
336
+ t.members.freeze
258
337
  t
259
338
  end
260
339
  end
340
+
341
+ # @example
342
+ # t = Type::Hash[Type::INT16]
343
+ Hash = HashFactory
344
+
345
+ # Syntactic helper for constructing a struct Type.
346
+ # You may be looking for {Data::Struct} instead.
347
+ # @example
348
+ # t = Type::Struct[Type::INT16, Type::STRING]
349
+ class StructFactory < Factory
350
+ # @param member_types [::Array<Type,SingleCompleteType>]
351
+ # @return [Type] (frozen)
352
+ def self.[](*member_types)
353
+ raise ArgumentError if member_types.empty?
354
+
355
+ t = Type.new(STRUCT, abstract: true)
356
+ member_types.each do |mt|
357
+ t << make_type(mt)
358
+ end
359
+ t.members.freeze
360
+ t
361
+ end
362
+ end
363
+
364
+ # @example
365
+ # t = Type::Struct[Type::INT16, Type::STRING]
366
+ Struct = StructFactory
261
367
  end
262
368
 
263
369
  # shortcuts
@@ -266,7 +372,7 @@ module DBus
266
372
  # This is prefered to {Type#initialize} which allows
267
373
  # incomplete or invalid types.
268
374
  # @param string_type [SingleCompleteType]
269
- # @return [DBus::Type]
375
+ # @return [DBus::Type] (frozen)
270
376
  # @raise SignatureException
271
377
  def type(string_type)
272
378
  Type::Parser.new(string_type).parse1
@@ -275,7 +381,7 @@ module DBus
275
381
 
276
382
  # Parse a String to zero or more {DBus::Type}s.
277
383
  # @param string_type [Signature]
278
- # @return [Array<DBus::Type>]
384
+ # @return [Array<DBus::Type>] (frozen)
279
385
  # @raise SignatureException
280
386
  def types(string_type)
281
387
  Type::Parser.new(string_type).parse
@@ -1534,6 +1534,34 @@
1534
1534
  - [0xDE, 0xAD, 0xBE, 0xEF]
1535
1535
  exc: DBus::InvalidPacketException
1536
1536
  msg: ''
1537
+ - sig: a{oq}
1538
+ end: little
1539
+ buf:
1540
+ # body size
1541
+ - [0, 0, 0, 0]
1542
+ # padding
1543
+ - [0, 0, 0, 0]
1544
+ val: {}
1545
+ - sig: a{oq}
1546
+ end: little
1547
+ buf:
1548
+ # body size
1549
+ - [26, 0, 0, 0]
1550
+ # dict_entry padding
1551
+ - [0, 0, 0, 0]
1552
+ # key, padding, value
1553
+ - [2, 0, 0, 0, "/7", 0]
1554
+ - 0
1555
+ - [7, 0]
1556
+ # dict_entry padding
1557
+ - [0, 0, 0, 0, 0, 0]
1558
+ # key, padding, value
1559
+ - [2, 0, 0, 0, "/9", 0]
1560
+ - 0
1561
+ - [9, 0]
1562
+ val:
1563
+ /7: 7
1564
+ /9: 9
1537
1565
  - sig: "(qq)"
1538
1566
  end: little
1539
1567
  buf:
data/spec/data_spec.rb CHANGED
@@ -85,6 +85,8 @@ end
85
85
  # TODO: Look at conversions? to_str, to_int?
86
86
 
87
87
  describe DBus::Data do
88
+ T = DBus::Type unless const_defined? "T"
89
+
88
90
  # test initialization, from user code, or from packet (from_raw)
89
91
  # remember to unpack if initializing from Data::Base
90
92
  # #value should recurse inside so that the user doesnt have to
@@ -183,6 +185,14 @@ describe DBus::Data do
183
185
 
184
186
  include_examples "constructor accepts plain or typed values", good
185
187
  include_examples "constructor rejects values from this list", bad
188
+
189
+ describe ".alignment" do
190
+ # this overly specific test avoids a redundant alignment call
191
+ # in the production code
192
+ it "returns the correct value" do
193
+ expect(described_class.alignment).to eq 4
194
+ end
195
+ end
186
196
  end
187
197
 
188
198
  describe DBus::Data::ObjectPath do
@@ -198,6 +208,14 @@ describe DBus::Data do
198
208
 
199
209
  include_examples "constructor accepts plain or typed values", good
200
210
  include_examples "constructor rejects values from this list", bad
211
+
212
+ describe ".alignment" do
213
+ # this overly specific test avoids a redundant alignment call
214
+ # in the production code
215
+ it "returns the correct value" do
216
+ expect(described_class.alignment).to eq 4
217
+ end
218
+ end
201
219
  end
202
220
 
203
221
  describe DBus::Data::Signature do
@@ -215,35 +233,50 @@ describe DBus::Data do
215
233
 
216
234
  include_examples "constructor accepts plain or typed values", good
217
235
  include_examples "constructor rejects values from this list", bad
236
+
237
+ describe ".alignment" do
238
+ # this overly specific test avoids a redundant alignment call
239
+ # in the production code
240
+ it "returns the correct value" do
241
+ expect(described_class.alignment).to eq 1
242
+ end
243
+ end
218
244
  end
219
245
  end
220
246
 
221
247
  describe "containers" do
222
248
  describe DBus::Data::Array do
223
249
  good = [
224
- # [[1, 2, 3], member_type: nil],
225
- [[1, 2, 3], { member_type: "q" }],
226
- [[1, 2, 3], { member_type: DBus::Type::UINT16 }],
227
- [[1, 2, 3], { member_type: DBus.type("q") }],
228
- [[DBus::Data::UInt16.new(1), DBus::Data::UInt16.new(2), DBus::Data::UInt16.new(3)], { member_type: "q" }]
250
+ # [[1, 2, 3], type: nil],
251
+ [[1, 2, 3], { type: "aq" }],
252
+ [[1, 2, 3], { type: T::Array[T::UINT16] }],
253
+ [[1, 2, 3], { type: T::Array["q"] }],
254
+ [[DBus::Data::UInt16.new(1), DBus::Data::UInt16.new(2), DBus::Data::UInt16.new(3)], { type: T::Array["q"] }]
229
255
  # TODO: others
230
256
  ]
231
257
 
232
258
  bad = [
233
259
  # undesirable type guessing
234
- ## [[1, 2, 3], { member_type: nil }, DBus::InvalidPacketException, "Unknown type code"],
235
- ## [[1, 2, 3], { member_type: "!" }, DBus::InvalidPacketException, "Unknown type code"]
260
+ ## [[1, 2, 3], { type: nil }, DBus::InvalidPacketException, "Unknown type code"],
261
+ ## [[1, 2, 3], { type: "!" }, DBus::InvalidPacketException, "Unknown type code"]
236
262
  # TODO: others
237
263
  ]
238
264
 
239
265
  include_examples "constructor (kwargs) accepts values", good
240
266
  include_examples "constructor (kwargs) rejects values", bad
267
+
268
+ describe ".from_typed" do
269
+ it "creates new instance from given object and type" do
270
+ type = T::Array[String]
271
+ expect(described_class.from_typed(["test", "lest"], type: type)).to be_a(described_class)
272
+ end
273
+ end
241
274
  end
242
275
 
243
276
  describe DBus::Data::Struct do
244
277
  three_words = ::Struct.new(:a, :b, :c)
245
278
 
246
- qqq = ["q", "q", "q"]
279
+ qqq = T::Struct[T::UINT16, T::UINT16, T::UINT16]
247
280
  integers = [1, 2, 3]
248
281
  uints = [DBus::Data::UInt16.new(1), DBus::Data::UInt16.new(2), DBus::Data::UInt16.new(3)]
249
282
 
@@ -260,36 +293,50 @@ describe DBus::Data do
260
293
  # TODO: also check data ownership: reasonable to own the data?
261
294
  # can make it explicit?
262
295
  good = [
263
- # from plain array; various m_t styles
264
- [integers, { member_types: ["q", "q", "q"] }],
265
- [integers, { member_types: [DBus::Type::UINT16, DBus::Type::UINT16, DBus::Type::UINT16] }],
266
- [integers, { member_types: DBus.types("qqq") }],
296
+ # from plain array; various *type* styles
297
+ [integers, { type: DBus.type("(qqq)") }],
298
+ [integers, { type: T::Struct["q", "q", "q"] }],
299
+ [integers, { type: T::Struct[T::UINT16, T::UINT16, T::UINT16] }],
300
+ [integers, { type: T::Struct[*DBus.types("qqq")] }],
267
301
  # plain array of data
268
- [uints, { member_types: DBus.types("qqq") }],
302
+ [uints, { type: qqq }],
269
303
  # ::Struct
270
- [three_words.new(*integers), { member_types: qqq }],
271
- [three_words.new(*uints), { member_types: qqq }]
304
+ [three_words.new(*integers), { type: qqq }],
305
+ [three_words.new(*uints), { type: qqq }]
272
306
  # TODO: others
273
307
  ]
274
308
 
309
+ # check these only when canonicalizing @value, because that will
310
+ # type-check the value deeply
275
311
  _bad_but_valid = [
276
- # Wrong member_types arg:
277
- # hmm this is another reason to pass the type
278
- # as the entire struct type, not the members:
279
- # empty struct will be caught naturally
280
- [integers, { member_types: [] }, ArgumentError, "???"],
281
- [integers, { member_types: ["!"] }, DBus::InvalidPacketException, "Unknown type code"],
282
312
  # STRUCT specific: member count mismatch
283
- [[1, 2], { member_types: DBus.types("qqq") }, ArgumentError, "???"],
284
- [[1, 2, 3, 4], { member_types: DBus.types("qqq") }, ArgumentError, "???"]
313
+ [[1, 2], { type: qqq }, ArgumentError, "???"],
314
+ [[1, 2, 3, 4], { type: qqq }, ArgumentError, "???"]
285
315
  # TODO: others
286
316
  ]
287
317
 
288
318
  include_examples "constructor (kwargs) accepts values", good
289
319
  # include_examples "constructor (kwargs) rejects values", bad
320
+
321
+ describe ".from_typed" do
322
+ it "creates new instance from given object and type" do
323
+ type = T::Struct[T::STRING, T::STRING]
324
+ expect(described_class.from_typed(["test", "lest"].freeze, type: type))
325
+ .to be_a(described_class)
326
+ end
327
+ end
290
328
  end
291
329
 
292
330
  describe DBus::Data::Variant do
331
+ describe ".from_typed" do
332
+ it "creates new instance from given object and type" do
333
+ type = DBus.type(T::VARIANT)
334
+ value = described_class.from_typed("test", type: type)
335
+ expect(value).to be_a(described_class)
336
+ expect(value.type.to_s).to eq "v"
337
+ expect(value.member_type.to_s).to eq "s"
338
+ end
339
+ end
293
340
  end
294
341
 
295
342
  describe DBus::Data::DictEntry do
@@ -29,6 +29,13 @@ describe DBus::PacketMarshaller do
29
29
  subject.append(signature, t.val)
30
30
  expect(subject.packet).to eq(expected)
31
31
  end
32
+
33
+ it "writes a '#{signature}' with typed value #{t.val.inspect} (#{endianness})" do
34
+ subject = described_class.new(endianness: endianness)
35
+ typed_val = DBus::Data.make_typed(signature, t.val)
36
+ subject.append(signature, typed_val)
37
+ expect(subject.packet).to eq(expected)
38
+ end
32
39
  end
33
40
  end
34
41
  end
@@ -4,6 +4,14 @@
4
4
  require_relative "spec_helper"
5
5
  require "dbus"
6
6
 
7
+ # FIXME: factor out DBus::TestFixtures::Value in spec_helper
8
+ require "ostruct"
9
+ require "yaml"
10
+
11
+ data_dir = File.expand_path("data", __dir__)
12
+ marshall_yaml_s = File.read("#{data_dir}/marshall.yaml")
13
+ marshall_yaml = YAML.safe_load(marshall_yaml_s)
14
+
7
15
  describe "PropertyTest" do
8
16
  before(:each) do
9
17
  @session_bus = DBus::ASessionBus.new
@@ -52,7 +60,7 @@ describe "PropertyTest" do
52
60
 
53
61
  it "tests get all" do
54
62
  all = @iface.all_properties
55
- expect(all.keys.sort).to eq(["MyStruct", "ReadMe", "ReadOrWriteMe"])
63
+ expect(all.keys.sort).to eq(["MyArray", "MyDict", "MyStruct", "MyVariant", "ReadMe", "ReadOrWriteMe"])
56
64
  end
57
65
 
58
66
  it "tests get all on a V1 object" do
@@ -60,7 +68,7 @@ describe "PropertyTest" do
60
68
  iface = obj["org.ruby.SampleInterface"]
61
69
 
62
70
  all = iface.all_properties
63
- expect(all.keys.sort).to eq(["MyStruct", "ReadMe", "ReadOrWriteMe"])
71
+ expect(all.keys.sort).to eq(["MyArray", "MyDict", "MyStruct", "MyVariant", "ReadMe", "ReadOrWriteMe"])
64
72
  end
65
73
 
66
74
  it "tests unknown property reading" do
@@ -147,4 +155,67 @@ describe "PropertyTest" do
147
155
  expect(reply).to match(/variant\s+struct {\s+string "three"\s+string "strings"\s+string "in a struct"\s+}/)
148
156
  end
149
157
  end
158
+
159
+ context "an array-typed property" do
160
+ it "gets read as an array" do
161
+ val = @iface["MyArray"]
162
+ expect(val).to eq([42, 43])
163
+ end
164
+ end
165
+
166
+ context "a dict-typed property" do
167
+ it "gets read as a hash" do
168
+ val = @iface["MyDict"]
169
+ expect(val).to eq({
170
+ "one" => 1,
171
+ "two" => "dva",
172
+ "three" => [3, 3, 3]
173
+ })
174
+ end
175
+
176
+ it "Get returns the correctly typed value (check with dbus-send)" do
177
+ cmd = "dbus-send --print-reply " \
178
+ "--dest=org.ruby.service " \
179
+ "/org/ruby/MyInstance " \
180
+ "org.freedesktop.DBus.Properties.Get " \
181
+ "string:org.ruby.SampleInterface " \
182
+ "string:MyDict"
183
+ reply = `#{cmd}`
184
+ # a bug about variant nesting lead to a "variant variant int32 1" value
185
+ match_rx = /variant \s+ array \s \[ \s+
186
+ dict \s entry\( \s+
187
+ string \s "one" \s+
188
+ variant \s+ int32 \s 1 \s+
189
+ \)/x
190
+ expect(reply).to match(match_rx)
191
+ end
192
+ end
193
+
194
+ context "a variant-typed property" do
195
+ it "gets read at all" do
196
+ obj = @svc.object("/org/ruby/MyDerivedInstance")
197
+ iface = obj["org.ruby.SampleInterface"]
198
+ val = iface["MyVariant"]
199
+ expect(val).to eq([42, 43])
200
+ end
201
+ end
202
+
203
+ context "marshall.yaml round-trip via a VARIANT property" do
204
+ marshall_yaml.each do |test|
205
+ t = OpenStruct.new(test)
206
+ next if t.val.nil?
207
+
208
+ # Round trips do not work yet because the properties
209
+ # must present a plain Ruby value so the exact D-Bus type is lost.
210
+ # Round trips will work once users can declare accepting DBus::Data
211
+ # in properties and method arguments.
212
+ it "Sets #{t.sig.inspect}:#{t.val.inspect} and Gets something back" do
213
+ before = DBus::Data.make_typed(t.sig, t.val)
214
+ expect { @iface["MyVariant"] = before }.to_not raise_error
215
+ expect { _after = @iface["MyVariant"] }.to_not raise_error
216
+ # round-trip:
217
+ # expect(after).to eq(before.value)
218
+ end
219
+ end
220
+ end
150
221
  end
@@ -13,16 +13,30 @@ PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
13
13
  class Test < DBus::Object
14
14
  Point2D = Struct.new(:x, :y)
15
15
 
16
+ attr_writer :main_loop
17
+
16
18
  INTERFACE = "org.ruby.SampleInterface"
17
19
  def initialize(path)
18
20
  super path
19
21
  @read_me = "READ ME"
20
22
  @read_or_write_me = "READ OR WRITE ME"
21
23
  @my_struct = ["three", "strings", "in a struct"].freeze
24
+ @my_array = [42, 43]
25
+ @my_dict = {
26
+ "one" => 1,
27
+ "two" => "dva",
28
+ "three" => [3, 3, 3]
29
+ }
30
+ @my_variant = @my_array.dup
31
+ @main_loop = nil
22
32
  end
23
33
 
24
34
  # Create an interface aggregating all upcoming dbus_method defines.
25
35
  dbus_interface INTERFACE do
36
+ dbus_method :quit, "" do
37
+ @main_loop&.quit
38
+ end
39
+
26
40
  dbus_method :hello, "in name:s, in name2:s" do |name, name2|
27
41
  puts "hello(#{name}, #{name2})"
28
42
  end
@@ -93,7 +107,10 @@ class Test < DBus::Object
93
107
  end
94
108
  dbus_reader :explosive, "s"
95
109
 
96
- dbus_attr_reader :my_struct, "(sss)"
110
+ dbus_attr_accessor :my_struct, "(sss)"
111
+ dbus_attr_accessor :my_array, "aq"
112
+ dbus_attr_accessor :my_dict, "a{sv}"
113
+ dbus_attr_accessor :my_variant, "v"
97
114
  end
98
115
 
99
116
  # closing and reopening the same interface
@@ -193,6 +210,7 @@ end
193
210
  puts "listening, with ruby-#{RUBY_VERSION}"
194
211
  main = DBus::Main.new
195
212
  main << bus
213
+ myobj.main_loop = main
196
214
  begin
197
215
  main.run
198
216
  rescue SystemCallError
data/spec/spec_helper.rb CHANGED
@@ -15,6 +15,8 @@ if coverage
15
15
  SimpleCov.add_filter "_spec.rb"
16
16
  # do not cover the activesupport helpers
17
17
  SimpleCov.add_filter "/core_ext/"
18
+ # measure all if/else branches on a line
19
+ SimpleCov.enable_coverage :branch
18
20
 
19
21
  SimpleCov.start
20
22
 
data/spec/type_spec.rb CHANGED
@@ -45,6 +45,7 @@ describe DBus do
45
45
  ["a{vs}", "DICT_ENTRY key must be basic (non-container)"],
46
46
  ["{sv}", "DICT_ENTRY not an immediate child of an ARRAY"],
47
47
  ["a({sv})", "DICT_ENTRY not an immediate child of an ARRAY"],
48
+ ["a{s", "DICT_ENTRY not closed"],
48
49
  ["a{sv", "DICT_ENTRY not closed"],
49
50
  ["}", "DICT_ENTRY unexpectedly closed"],
50
51
 
@@ -79,4 +80,108 @@ describe DBus do
79
80
  end
80
81
  end
81
82
  end
83
+
84
+ describe DBus::Type do
85
+ describe "#<<" do
86
+ it "raises if the argument is not a Type" do
87
+ t = DBus::Type.new(DBus::Type::ARRAY)
88
+ expect { t << "s" }.to raise_error(ArgumentError)
89
+ end
90
+
91
+ # TODO: the following raise checks do not occur in practice, as there are
92
+ # parallel checks in the parses. The code could be simplified?
93
+ it "raises if adding too much to an array" do
94
+ t = DBus::Type.new(DBus::Type::ARRAY)
95
+ b = DBus::Type.new(DBus::Type::BOOLEAN)
96
+ t << b
97
+ expect { t << b }.to raise_error(DBus::Type::SignatureException)
98
+ end
99
+
100
+ it "raises if adding too much to a dict_entry" do
101
+ t = DBus::Type.new(DBus::Type::DICT_ENTRY, abstract: true)
102
+ b = DBus::Type.new(DBus::Type::BOOLEAN)
103
+ t << b
104
+ t << b
105
+ expect { t << b }.to raise_error(DBus::Type::SignatureException)
106
+ end
107
+
108
+ it "raises if adding to a non-container" do
109
+ t = DBus::Type.new(DBus::Type::STRING)
110
+ b = DBus::Type.new(DBus::Type::BOOLEAN)
111
+ expect { t << b }.to raise_error(DBus::Type::SignatureException)
112
+
113
+ t = DBus::Type.new(DBus::Type::VARIANT)
114
+ expect { t << b }.to raise_error(DBus::Type::SignatureException)
115
+ end
116
+ end
117
+
118
+ describe DBus::Type::Array do
119
+ describe ".[]" do
120
+ it "takes Type argument" do
121
+ t = DBus::Type::Array[DBus::Type.new("s")]
122
+ expect(t.to_s).to eq "as"
123
+ end
124
+
125
+ it "takes 's':String argument" do
126
+ t = DBus::Type::Array["s"]
127
+ expect(t.to_s).to eq "as"
128
+ end
129
+
130
+ it "takes String:Class argument" do
131
+ t = DBus::Type::Array[String]
132
+ expect(t.to_s).to eq "as"
133
+ end
134
+
135
+ it "rejects Integer:Class argument" do
136
+ expect { DBus::Type::Array[Integer] }.to raise_error(ArgumentError)
137
+ end
138
+
139
+ it "rejects /./:Regexp argument" do
140
+ expect { DBus::Type::Array[/./] }.to raise_error(ArgumentError)
141
+ end
142
+ end
143
+ end
144
+
145
+ describe DBus::Type::Hash do
146
+ describe ".[]" do
147
+ it "takes Type arguments" do
148
+ t = DBus::Type::Hash[DBus::Type.new("s"), DBus::Type.new("v")]
149
+ expect(t.to_s).to eq "a{sv}"
150
+ end
151
+
152
+ it "takes 's':String arguments" do
153
+ t = DBus::Type::Hash["s", "v"]
154
+ expect(t.to_s).to eq "a{sv}"
155
+ end
156
+
157
+ it "takes String:Class argument" do
158
+ t = DBus::Type::Hash[String, DBus::Type::VARIANT]
159
+ expect(t.to_s).to eq "a{sv}"
160
+ end
161
+ end
162
+ end
163
+
164
+ describe DBus::Type::Struct do
165
+ describe ".[]" do
166
+ it "takes Type arguments" do
167
+ t = DBus::Type::Struct[DBus::Type.new("s"), DBus::Type.new("v")]
168
+ expect(t.to_s).to eq "(sv)"
169
+ end
170
+
171
+ it "takes 's':String arguments" do
172
+ t = DBus::Type::Struct["s", "v"]
173
+ expect(t.to_s).to eq "(sv)"
174
+ end
175
+
176
+ it "takes String:Class argument" do
177
+ t = DBus::Type::Struct[String, DBus::Type::VARIANT]
178
+ expect(t.to_s).to eq "(sv)"
179
+ end
180
+
181
+ it "raises on no arguments" do
182
+ expect { DBus::Type::Struct[] }.to raise_error(ArgumentError)
183
+ end
184
+ end
185
+ end
186
+ end
82
187
  end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe "Quit the service" do
8
+ it "Tells the service to quit and waits, to collate coverage data" do
9
+ session_bus = DBus::ASessionBus.new
10
+ @svc = session_bus.service("org.ruby.service")
11
+ @obj = @svc.object("/org/ruby/MyInstance")
12
+ @obj.default_iface = "org.ruby.SampleInterface"
13
+ @obj.quit
14
+ sleep 3
15
+ end
16
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-dbus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0.beta2
4
+ version: 0.18.0.beta5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruby DBus Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-04 00:00:00.000000000 Z
11
+ date: 2022-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rexml
@@ -197,6 +197,7 @@ files:
197
197
  - spec/type_spec.rb
198
198
  - spec/value_spec.rb
199
199
  - spec/variant_spec.rb
200
+ - spec/zzz_quit_spec.rb
200
201
  homepage: https://github.com/mvidner/ruby-dbus
201
202
  licenses:
202
203
  - LGPL-2.1