ruby-dbus 0.18.0.beta4 → 0.18.0.beta7

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: e6e093b55c88614ceebd2cfd0c09a5bba3886b8c561b8e153c034d9249ae5a08
4
- data.tar.gz: a2b2931d1b3b9ae560cc6204e297a7daceec2ec85a065a2782cbbeed34286f5d
3
+ metadata.gz: 56815cd47f001f19c6d74cc02f0f1017e6e17d8df44e0949f020e4932adbc622
4
+ data.tar.gz: 4aeda5f0c4972671adf6ae24554aa08ce5e6208846f87f5793562e73b8e7d632
5
5
  SHA512:
6
- metadata.gz: 66e7ad826a0f1ef5bb1abba72a6f76f02fbd4c13c7e7f14caf3abd71946ba4e5a0b9cc16950d60a22eb2d994d73c78306d270296af90f2bf9aa2aaaf4f85dab3
7
- data.tar.gz: 4641ca7b18414d7cf58d786ab05d3d3bc3a50d40311b1b5650b6f44a1bfcf0b1cca373eee4445b5cd6e0eb70a42407b244d93c09371d9ea985a66d6d4c79c0d7
6
+ metadata.gz: 2432d8ada23410027d00c7995c7514e3e927430844cf037fa8496509c47b10b4f5a431335762206bfc9966724d6d84014f57d9bbe31613bfd253145e25b82b77
7
+ data.tar.gz: c1b480afebcdaf53b6fbe084f6150b7c9758d1329a207b4941dd8256cbc3786d42341564b3b8220d9e897e7487d281fede15eab8a31d81741582272519e1ac19
data/NEWS.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Ruby D-Bus 0.18.0.beta7 - 2022-05-29
6
+
7
+ API:
8
+ * DBus.variant(type, value) is deprecated in favor of
9
+ Data::Variant.new(value, member_type:)
10
+
11
+ Bug fixes:
12
+ * Client-side properties: When calling Properties.Set in
13
+ ProxyObjectInterface#[]=, use the correct type ([#108][]).
14
+
15
+ [#108]: https://github.com/mvidner/ruby-dbus/issues/108
16
+
17
+ ## Ruby D-Bus 0.18.0.beta6 - 2022-05-25
18
+
19
+ API:
20
+ * Data::Base#value returns plain Ruby types;
21
+ Data::Container#exact_value contains Data::Base ([#114][]).
22
+ * Data::Base#initialize and .from_typed allow plain or exact values, validate
23
+ argument types.
24
+ * Implement #== (converting) and #eql? (strict) for Data::Base and DBus::Type.
25
+
26
+ [#114]: https://github.com/mvidner/ruby-dbus/pull/114
27
+
28
+ ## Ruby D-Bus 0.18.0.beta5 - 2022-04-27
29
+
30
+ API:
31
+ * DBus::Type instances are frozen.
32
+ * Data::Container classes (Array, Struct, DictEntry, but not Variant)
33
+ constructors (#initialize, .from_items, .from_typed) changed to have
34
+ a *type* argument instead of *member_type* or *member_types*.
35
+ * Added type factories
36
+ * Type::Array[type]
37
+ * Type::Hash[key_type, value_type]
38
+ * Type::Struct[type1, type2...]
39
+
40
+ Bug fixes:
41
+ * Properties containing Variants would return them doubly wrapped ([#111][]).
42
+
43
+ [#111]: https://github.com/mvidner/ruby-dbus/pull/111
44
+
5
45
  ## Ruby D-Bus 0.18.0.beta4 - 2022-04-21
6
46
 
7
47
  Bug fixes:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.18.0.beta4
1
+ 0.18.0.beta7
data/doc/Reference.md CHANGED
@@ -166,12 +166,21 @@ D-Bus has stricter typing than Ruby, so the library must decide
166
166
  which D-Bus type to choose. Most of the time the choice is dictated
167
167
  by the D-Bus signature.
168
168
 
169
+ For exact representation of D-Bus data types, use subclasses
170
+ of {DBus::Data::Base}, such as {DBus::Data::Int16} or {DBus::Data::UInt64}.
171
+
169
172
  ##### Variants
170
173
 
171
174
  If the signature expects a Variant
172
175
  (which is the case for all Properties!) then an explicit mechanism is needed.
173
176
 
174
- 1. A pair [{DBus::Type}, value] specifies to marshall *value* as
177
+ 1. Any {DBus::Data::Base}.
178
+
179
+ 2. A {DBus::Data::Variant} made by {DBus.variant}(signature, value).
180
+ (Formerly this produced the type+value pair below, now it is just an alias
181
+ to the Variant constructor.)
182
+
183
+ 3. A pair [{DBus::Type}, value] specifies to marshall *value* as
175
184
  that specified type.
176
185
  The pair can be produced by {DBus.variant}(signature, value) which
177
186
  gives the same result as [{DBus.type}(signature), value].
@@ -181,13 +190,13 @@ If the signature expects a Variant
181
190
 
182
191
  `foo_i["Bar"] = DBus.variant("au", [0, 1, 1, 2, 3, 5, 8])`
183
192
 
184
- 2. Other values are tried to fit one of these:
193
+ 4. Other values are tried to fit one of these:
185
194
  Boolean, Double, Array of Variants, Hash of String keyed Variants,
186
195
  String, Int32, Int64.
187
196
 
188
- 3. **Deprecated:** A pair [String, value], where String is a valid
197
+ 5. **Deprecated:** A pair [String, value], where String is a valid
189
198
  signature of a single complete type, marshalls value as that
190
- type. This will hit you when you rely on method (2) but happen to have
199
+ type. This will hit you when you rely on method (4) but happen to have
191
200
  a particular string value in an array.
192
201
 
193
202
  ##### Structs
data/lib/dbus/data.rb CHANGED
@@ -35,7 +35,7 @@ module DBus
35
35
  # construct an appropriate {Data::Base} instance.
36
36
  #
37
37
  # @param type [SingleCompleteType,Type]
38
- # @param value [::Object]
38
+ # @param value [::Object,Data::Base] a plain value; exact values also allowed
39
39
  # @return [Data::Base]
40
40
  # @raise TypeError
41
41
  def make_typed(type, value)
@@ -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
 
@@ -58,15 +58,28 @@ module DBus
58
58
  # @!method self.fixed?
59
59
  # @return [Boolean]
60
60
 
61
- # @return appropriately-typed, valid value
61
+ # @return [::Object] a valid value, plain-Ruby typed.
62
+ # @see Data::Container#exact_value
62
63
  attr_reader :value
63
64
 
65
+ # @!method self.type_code
66
+ # @return [String] a single-character string, for example "a" for arrays
67
+
64
68
  # @!method type
65
69
  # @abstract
66
70
  # Note that for Variants type=="v",
67
71
  # for the specific see {Variant#member_type}
68
72
  # @return [Type] the exact type of this value
69
73
 
74
+ # @!method self.from_typed(value, type:)
75
+ # @param value [::Object]
76
+ # @param type [Type]
77
+ # @return [Base]
78
+ # @api private
79
+ # Use {Data.make_typed} instead.
80
+ # Construct an instance of the specific subclass, with a type further
81
+ # specified in the *type* argument.
82
+
70
83
  # Child classes must validate *value*.
71
84
  def initialize(value)
72
85
  @value = value
@@ -82,7 +95,19 @@ module DBus
82
95
 
83
96
  # Hash key equality
84
97
  # See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F
85
- alias eql? ==
98
+ # Stricter than #== (RSpec: eq), 1==1.0 but 1.eql(1.0)->false
99
+ def eql?(other)
100
+ return false unless other.class == self.class
101
+
102
+ other.value.eql?(@value)
103
+ # TODO: this should work, now check derived classes, exact_value
104
+ end
105
+
106
+ # @param type [Type]
107
+ def self.assert_type_matches_class(type)
108
+ raise ArgumentError, "Expecting #{type_code.inspect} for class #{self}, got #{type.sigtype.inspect}" \
109
+ unless type.sigtype == type_code
110
+ end
86
111
  end
87
112
 
88
113
  # A value that is not a {Container}.
@@ -103,10 +128,10 @@ module DBus
103
128
  end
104
129
 
105
130
  # @param value [::Object]
106
- # @param member_types [::Array<Type>] (ignored, will be empty)
131
+ # @param type [Type]
107
132
  # @return [Basic]
108
- def self.from_typed(value, member_types:) # rubocop:disable Lint/UnusedMethodArgument
109
- # assert member_types.empty?
133
+ def self.from_typed(value, type:)
134
+ assert_type_matches_class(type)
110
135
  new(value)
111
136
  end
112
137
  end
@@ -132,34 +157,6 @@ module DBus
132
157
  end
133
158
  end
134
159
 
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
160
  # Format strings for String#unpack, both little- and big-endian.
164
161
  Format = ::Struct.new(:little, :big)
165
162
 
@@ -398,6 +395,23 @@ module DBus
398
395
  end
399
396
  end
400
397
 
398
+ # {DBus::Data::String}, {DBus::Data::ObjectPath}, or {DBus::Data::Signature}.
399
+ class StringLike < Basic
400
+ def self.fixed?
401
+ false
402
+ end
403
+
404
+ def initialize(value)
405
+ if value.is_a?(self.class)
406
+ value = value.value
407
+ else
408
+ self.class.validate_raw!(value)
409
+ end
410
+
411
+ super(value)
412
+ end
413
+ end
414
+
401
415
  # UTF-8 encoded string.
402
416
  class String < StringLike
403
417
  def self.type_code
@@ -477,7 +491,7 @@ module DBus
477
491
  Byte
478
492
  end
479
493
 
480
- # @return [Array<Type>]
494
+ # @return [::Array<Type>]
481
495
  def self.validate_raw!(value)
482
496
  DBus.types(value)
483
497
  rescue Type::SignatureException => e
@@ -494,6 +508,44 @@ module DBus
494
508
  end
495
509
  end
496
510
 
511
+ # Contains one or more other values.
512
+ class Container < Base
513
+ def self.basic?
514
+ false
515
+ end
516
+
517
+ def self.fixed?
518
+ false
519
+ end
520
+
521
+ # For containers, the type varies among instances
522
+ # @see Base#type
523
+ attr_reader :type
524
+
525
+ # @return something that is, or contains, {Data::Base}.
526
+ # Er, this docs kinda sucks.
527
+ def exact_value
528
+ @value
529
+ end
530
+
531
+ def value
532
+ @value.map(&:value)
533
+ end
534
+
535
+ # Hash key equality
536
+ # See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F
537
+ # Stricter than #== (RSpec: eq), 1==1.0 but 1.eql(1.0)->false
538
+ def eql?(other)
539
+ return false unless other.class == self.class
540
+
541
+ other.exact_value.eql?(exact_value)
542
+ end
543
+
544
+ # def ==(other)
545
+ # eql?(other) || super
546
+ # end
547
+ end
548
+
497
549
  # An Array, or a Dictionary (Hash).
498
550
  class Array < Container
499
551
  def self.type_code
@@ -504,44 +556,33 @@ module DBus
504
556
  4
505
557
  end
506
558
 
507
- # @return [Type]
508
- attr_reader :member_type
509
-
510
- def type
511
- return @type if @type
512
-
513
- # TODO: reconstructing the type is cumbersome; have #initialize take *type* instead?
514
- # TODO: or rather add Type::Array[t]
515
- @type = Type.new("a")
516
- @type << member_type
517
- @type
518
- end
519
-
520
559
  # TODO: check that Hash keys are basic types
521
560
  # @param mode [:plain,:exact]
522
- # @param member_type [Type]
561
+ # @param type [Type]
523
562
  # @param hash [Boolean] are we unmarshalling an ARRAY of DICT_ENTRY
524
563
  # @return [Data::Array]
525
- def self.from_items(value, mode:, member_type:, hash: false)
564
+ def self.from_items(value, mode:, type:, hash: false)
526
565
  value = Hash[value] if hash
527
566
  return value if mode == :plain
528
567
 
529
- new(value, member_type: member_type)
568
+ new(value, type: type)
530
569
  end
531
570
 
532
571
  # @param value [::Object]
533
- # @param member_types [::Array<Type>]
572
+ # @param type [Type]
534
573
  # @return [Data::Array]
535
- def self.from_typed(value, member_types:)
536
- # TODO: validation
537
- member_type = member_types.first
574
+ def self.from_typed(value, type:)
575
+ new(value, type: type) # initialize(::Array<Data::Base>)
576
+ end
538
577
 
539
- # TODO: Dict??
540
- items = value.map do |i|
541
- Data.make_typed(member_type, i)
578
+ def value
579
+ v = super
580
+ if type.child.sigtype == Type::DICT_ENTRY
581
+ # BTW this makes a copy so mutating it is pointless
582
+ v.to_h
583
+ else
584
+ v
542
585
  end
543
-
544
- new(items, member_type: member_type) # initialize(::Array<Data::Base>)
545
586
  end
546
587
 
547
588
  # FIXME: should Data::Array be mutable?
@@ -550,13 +591,29 @@ module DBus
550
591
  # TODO: specify type or guess type?
551
592
  # Data is the exact type, so its constructor should be exact
552
593
  # and guesswork should be clearly labeled
553
- # @param member_type [SingleCompleteType,Type]
554
- def initialize(value, member_type:)
555
- member_type = DBus.type(member_type) unless member_type.is_a?(Type)
556
- # TODO: copy from another Data::Array
557
- @member_type = member_type
558
- @type = nil
559
- super(value)
594
+
595
+ # @param value [Data::Array,Enumerable]
596
+ # @param type [SingleCompleteType,Type]
597
+ def initialize(value, type:)
598
+ type = Type::Factory.make_type(type)
599
+ self.class.assert_type_matches_class(type)
600
+ @type = type
601
+
602
+ typed_value = case value
603
+ when Data::Array
604
+ unless value.type == type
605
+ raise ArgumentError,
606
+ "Specified type is #{type.inspect} but value type is #{value.type.inspect}"
607
+ end
608
+
609
+ value.exact_value
610
+ else
611
+ # TODO: Dict??
612
+ value.map do |i|
613
+ Data.make_typed(type.child, i)
614
+ end
615
+ end
616
+ super(typed_value)
560
617
  end
561
618
  end
562
619
 
@@ -572,51 +629,86 @@ module DBus
572
629
  8
573
630
  end
574
631
 
575
- # @return [::Array<Type>]
576
- attr_reader :member_types
577
-
578
- def type
579
- return @type if @type
580
-
581
- # TODO: reconstructing the type is cumbersome; have #initialize take *type* instead?
582
- # TODO: or rather add Type::Struct[t1, t2, ...]
583
- @type = Type.new(self.class.type_code, abstract: true)
584
- @member_types.each do |member_type|
585
- @type << member_type
586
- end
587
- @type
588
- end
589
-
590
632
  # @param value [::Array]
591
- def self.from_items(value, mode:, member_types:)
633
+ def self.from_items(value, mode:, type:)
592
634
  value.freeze
593
635
  return value if mode == :plain
594
636
 
595
- new(value, member_types: member_types)
637
+ new(value, type: type)
596
638
  end
597
639
 
598
640
  # @param value [::Object] (#size, #each)
599
- # @param member_types [::Array<Type>]
641
+ # @param type [Type]
600
642
  # @return [Struct]
601
- def self.from_typed(value, member_types:)
602
- # TODO: validation
603
- raise unless value.size == member_types.size
643
+ def self.from_typed(value, type:)
644
+ new(value, type: type)
645
+ end
646
+
647
+ # @param value [Data::Struct,Enumerable]
648
+ # @param type [SingleCompleteType,Type]
649
+ def initialize(value, type:)
650
+ type = Type::Factory.make_type(type)
651
+ self.class.assert_type_matches_class(type)
652
+ @type = type
653
+
654
+ typed_value = case value
655
+ when self.class
656
+ unless value.type == type
657
+ raise ArgumentError,
658
+ "Specified type is #{type.inspect} but value type is #{value.type.inspect}"
659
+ end
660
+
661
+ value.exact_value
662
+ else
663
+ member_types = type.members
664
+ unless value.size == member_types.size
665
+ raise ArgumentError, "Specified type has #{member_types.size} members " \
666
+ "but value has #{value.size} members"
667
+ end
668
+
669
+ member_types.zip(value).map do |item_type, item|
670
+ Data.make_typed(item_type, item)
671
+ end
672
+ end
673
+ super(typed_value)
674
+ end
604
675
 
605
- items = member_types.zip(value).map do |item_type, item|
606
- Data.make_typed(item_type, item)
676
+ def ==(other)
677
+ case other
678
+ when ::Struct
679
+ @value.size == other.size &&
680
+ @value.zip(other.to_a).all? { |i, other_i| i == other_i }
681
+ else
682
+ super
607
683
  end
684
+ end
685
+ end
608
686
 
609
- new(items, member_types: member_types) # initialize(::Array<Data::Base>)
687
+ # Dictionary/Hash entry.
688
+ # TODO: shouldn't instantiate?
689
+ class DictEntry < Struct
690
+ def self.type_code
691
+ "e"
610
692
  end
611
693
 
612
- def initialize(value, member_types:)
613
- @member_types = member_types
614
- @type = nil
615
- super(value)
694
+ # @param value [::Array]
695
+ def self.from_items(value, mode:, type:) # rubocop:disable Lint/UnusedMethodArgument
696
+ value.freeze
697
+ # DictEntry ignores the :exact mode
698
+ value
699
+ end
700
+
701
+ # @param value [::Object] (#size, #each)
702
+ # @param type [Type]
703
+ # @return [DictEntry]
704
+ def self.from_typed(value, type:)
705
+ new(value, type: type)
616
706
  end
617
707
  end
618
708
 
619
- # A generic type
709
+ # A generic type.
710
+ #
711
+ # Implementation note: @value is a {Data::Base}.
620
712
  class Variant < Container
621
713
  def self.type_code
622
714
  "v"
@@ -626,6 +718,10 @@ module DBus
626
718
  1
627
719
  end
628
720
 
721
+ def value
722
+ @value.value
723
+ end
724
+
629
725
  # @param member_type [Type]
630
726
  def self.from_items(value, mode:, member_type:)
631
727
  return value if mode == :plain
@@ -634,12 +730,12 @@ module DBus
634
730
  end
635
731
 
636
732
  # @param value [::Object]
637
- # @param member_types [::Array<Type>]
733
+ # @param type [Type]
638
734
  # @return [Variant]
639
- def self.from_typed(value, member_types:) # rubocop:disable Lint/UnusedMethodArgument
640
- # assert member_types.empty?
735
+ def self.from_typed(value, type:)
736
+ assert_type_matches_class(type)
641
737
 
642
- # decide on type of value
738
+ # nil: decide on type of value
643
739
  new(value, member_type: nil)
644
740
  end
645
741
 
@@ -659,78 +755,59 @@ module DBus
659
755
  # @return [Type]
660
756
  attr_reader :member_type
661
757
 
758
+ # Determine the type of *value*
759
+ # @param value [::Object]
760
+ # @return [Type]
761
+ # @api private
762
+ # See also {PacketMarshaller.make_variant}
662
763
  def self.guess_type(value)
663
764
  sct, = PacketMarshaller.make_variant(value)
664
765
  DBus.type(sct)
665
766
  end
666
767
 
667
- # @param member_type [Type,nil]
768
+ # @param member_type [SingleCompleteType,Type,nil]
668
769
  def initialize(value, member_type:)
770
+ member_type = Type::Factory.make_type(member_type) if member_type
669
771
  # TODO: validate that the given *member_type* matches *value*
670
- if value.is_a?(self.class)
772
+ case value
773
+ when Data::Variant
671
774
  # Copy the contained value instead of boxing it more
672
775
  # TODO: except perhaps for round-tripping in exact mode?
673
776
  @member_type = value.member_type
674
- value = value.value
777
+ value = value.exact_value
778
+ when Data::Base
779
+ @member_type = member_type || value.type
780
+ raise ArgumentError, "Variant type #{@member_type} does not match value type #{value.type}" \
781
+ unless @member_type == value.type
675
782
  else
676
783
  @member_type = member_type || self.class.guess_type(value)
784
+ value = Data.make_typed(@member_type, value)
677
785
  end
678
786
  super(value)
679
787
  end
680
- end
681
788
 
682
- # Dictionary/Hash entry.
683
- # TODO: shouldn't instantiate?
684
- class DictEntry < Container
685
- def self.type_code
686
- "e"
687
- end
688
-
689
- def self.alignment
690
- 8
691
- end
692
-
693
- # @return [::Array<Type>]
694
- attr_reader :member_types
695
-
696
- def type
697
- return @type if @type
698
-
699
- # TODO: reconstructing the type is cumbersome; have #initialize take *type* instead?
700
- @type = Type.new(self.class.type_code, abstract: true)
701
- @member_types.each do |member_type|
702
- @type << member_type
789
+ # Internal helpers to keep the {DBus.variant} method working.
790
+ # Formerly it returned just a pair of [DBus.type(string_type), value]
791
+ # so let's provide [0], [1], .first, .last
792
+ def [](index)
793
+ case index
794
+ when 0
795
+ member_type
796
+ when 1
797
+ value
798
+ else
799
+ raise ArgumentError, "DBus.variant can only be indexed with 0 or 1, seen #{index.inspect}"
703
800
  end
704
- @type
705
- end
706
-
707
- # @param value [::Array]
708
- def self.from_items(value, mode:, member_types:) # rubocop:disable Lint/UnusedMethodArgument
709
- value.freeze
710
- # DictEntry ignores the :exact mode
711
- value
712
801
  end
713
802
 
714
- # @param value [::Object] (#size, #each)
715
- # @param member_types [::Array<Type>]
716
- # @return [DictEntry]
717
- def self.from_typed(value, member_types:)
718
- # assert member_types.size == 2
719
- # TODO: duplicated from Struct. Inherit/delegate?
720
- # TODO: validation
721
- raise unless value.size == member_types.size
722
-
723
- items = member_types.zip(value).map do |item_type, item|
724
- Data.make_typed(item_type, item)
725
- end
726
-
727
- new(items, member_types: member_types) # initialize(::Array<Data::Base>)
803
+ # @see #[]
804
+ def first
805
+ self[0]
728
806
  end
729
807
 
730
- def initialize(value, member_types:)
731
- @member_types = member_types
732
- @type = nil
733
- super(value)
808
+ # @see #[]
809
+ def last
810
+ self[1]
734
811
  end
735
812
  end
736
813
 
@@ -238,19 +238,20 @@ module DBus
238
238
  # An (exported) property
239
239
  # https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
240
240
  class Property
241
- # @return [String] The name of the property, for example FooBar.
241
+ # @return [Symbol] The name of the property, for example FooBar.
242
242
  attr_reader :name
243
243
  # @return [SingleCompleteType]
244
244
  attr_reader :type
245
245
  # @return [Symbol] :read :write or :readwrite
246
246
  attr_reader :access
247
247
 
248
- # @return [Symbol] What to call at Ruby side.
248
+ # @return [Symbol,nil] What to call at Ruby side.
249
249
  # (Always without the trailing `=`)
250
+ # It is `nil` IFF representing a client-side proxy.
250
251
  attr_reader :ruby_name
251
252
 
252
253
  def initialize(name, type, access, ruby_name:)
253
- @name = name
254
+ @name = name.to_sym
254
255
  @type = type
255
256
  @access = access
256
257
  @ruby_name = ruby_name
@@ -270,5 +271,14 @@ module DBus
270
271
  def to_xml
271
272
  " <property type=\"#{@type}\" name=\"#{@name}\" access=\"#{@access}\"/>\n"
272
273
  end
274
+
275
+ # @param xml_node [AbstractXML::Node]
276
+ # @return [Property]
277
+ def self.from_xml(xml_node)
278
+ name = xml_node["name"].to_sym
279
+ type = xml_node["type"]
280
+ access = xml_node["access"].to_sym
281
+ new(name, type, access, ruby_name: nil)
282
+ end
273
283
  end
274
284
  end