ruby-dbus 0.18.0.beta2 → 0.18.0.beta5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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