firm 0.9.7 → 0.9.8

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: 8a4a4e31a319e92f4e5efb708562832f1072602084f5c0b4907c88d1e28d2c1b
4
- data.tar.gz: 0e9e64b8fb301c3f895fe0b76978d6794389d0ca651339fd132396866d05eefa
3
+ metadata.gz: 70fc573f0deb51e97747caf3a36ccfabb8e93a0ff8cb72f0de16e877eeb9713b
4
+ data.tar.gz: 66811b4933baeaa1e555223fa554292339534a5de8d4de66bbd2fe4d26db1633
5
5
  SHA512:
6
- metadata.gz: 6144308bc7ba7bba7cac24c173a8fbabcac75e3074d29205593c64173696c7b9c41b47c5747b1265ad680f437d87abbce400e9ea0f311b8417403be4faeb55a8
7
- data.tar.gz: 32353706f381e81dcacb705bbe144464fef1233df920dcb45d21e3386fca873bbd6ae52b7a1825cff84fec88d43a68935c89ad7577e9fa8f31f1e618c6cba8c4
6
+ metadata.gz: a37bb0eb3d1aaffed3531979640070f7048e899c93206c61f2e9377bf256f923b4c70f19d1cd8bedc486a94b124803e6f7cd2b42419a65c2746e8f19ca1db432
7
+ data.tar.gz: 8fe43c7d9828632f27c0e12271250c85b06662ffd9969d3feefb4c9e78f653fc993c42361fcb38dc8e719c2c75601e9da603b26c8ee83736b9d3d991750ef963
data/README.md CHANGED
@@ -50,7 +50,7 @@ resolve Class objects from these names if really needed.
50
50
  FIRM also supports a simple scheme to provide (de-)serialization support for user defined classes.
51
51
 
52
52
  FIRM provides object aliasing support for JSON and XML in a similar fashion as the standard support provided
53
- by YAML. All user defined serializable class as well as **named** `Struct`-derived classes support aliasing.
53
+ by YAML.
54
54
 
55
55
  FIRM also automatically recognizes and handles cyclic references of aliasable objects.
56
56
 
@@ -44,15 +44,64 @@ module FIRM
44
44
  alias key? include?
45
45
  end
46
46
 
47
- # Mixin module to patch singleton_clas of the Hash class to make Hash-es
48
- # JSON creatable (#json_creatable? returns true).
49
- module HashClassPatch
50
- # Create a new Hash instance from deserialized JSON data.
51
- # @param [Hash] object deserialized JSON object
52
- # @return [Hash] restored Hash instance
53
- def json_create(object)
54
- object['data'].to_h
47
+ module ContainerPatch
48
+
49
+ def self.included(base)
50
+ class << base
51
+ def json_new(object, &block)
52
+ # deserializing (anchor) object or alias
53
+ if object.has_key?('*id')
54
+ if FIRM::Serializable::Aliasing.restored?(self, object['*id'])
55
+ # resolving an already restored anchor for this alias
56
+ FIRM::Serializable::Aliasing.resolve_anchor(self, object['*id'])
57
+ else
58
+ # in case of cyclic references JSON will restore aliases before the anchors
59
+ # so in this case we instantiate an instance here and register it as
60
+ # the anchor; when the anchor is restored it will replace the contents of this
61
+ # instance with the restored elements
62
+ FIRM::Serializable::Aliasing.restore_anchor(object['*id'], self.new)
63
+ end
64
+ else
65
+ instance = if object.has_key?('&id')
66
+ anchor_id = object['&id'] # extract anchor id
67
+ if FIRM::Serializable::Aliasing.restored?(self, anchor_id)
68
+ # in case of cyclic references an alias will already have restored the anchor instance
69
+ # (default constructed); retrieve that instance here for deserialization of properties
70
+ FIRM::Serializable::Aliasing.resolve_anchor(self, anchor_id)
71
+ else
72
+ # restore the anchor here with a newly instantiated instance
73
+ FIRM::Serializable::Aliasing.restore_anchor(anchor_id, self.new)
74
+ end
75
+ else
76
+ self.new
77
+ end
78
+ block.call(instance)
79
+ instance
80
+ end
81
+ end
82
+ private :json_new
83
+ end
55
84
  end
85
+
86
+ def build_json(&block)
87
+ json_data = {
88
+ ::JSON.create_id => self.class.name
89
+ }
90
+ if (anchor = FIRM::Serializable::Aliasing.get_anchor(self))
91
+ anchor_data = FIRM::Serializable::Aliasing.get_anchor_data(self)
92
+ # retroactively insert the anchor in the anchored instance's serialization data
93
+ anchor_data['&id'] = anchor unless anchor_data.has_key?('&id')
94
+ json_data['*id'] = anchor
95
+ else
96
+ # register anchor object **before** serializing properties to properly handle cycling (bidirectional
97
+ # references)
98
+ FIRM::Serializable::Aliasing.register_anchor_object(self, json_data)
99
+ block.call(json_data)
100
+ end
101
+ json_data
102
+ end
103
+ private :build_json
104
+
56
105
  end
57
106
 
58
107
  class << self
@@ -131,8 +180,8 @@ module FIRM
131
180
  # enable safe deserializing
132
181
  self.start_safe_deserialize
133
182
  ::JSON.parse!(source,
134
- **{create_additions: true,
135
- object_class: Serializable::JSON::ObjectHash})
183
+ create_additions: true,
184
+ object_class: Serializable::JSON::ObjectHash)
136
185
  ensure
137
186
  # reset safe deserializing
138
187
  self.end_safe_deserialize
@@ -279,100 +328,101 @@ class ::Class
279
328
  end
280
329
 
281
330
  class ::Array
331
+ include FIRM::Serializable::JSON::ContainerPatch
332
+
333
+ class << self
334
+ # Create a new Array instance from deserialized JSON data.
335
+ # @param [Hash] object deserialized JSON object
336
+ # @return [Array] restored Array instance
337
+ def json_create(object)
338
+ json_new(object) { |instance| instance.replace(object['data']) }
339
+ end
340
+ end
341
+
282
342
  def as_json(*)
283
- collect { |e| e.respond_to?(:as_json) ? e.as_json : e }
343
+ build_json do |json_data|
344
+ json_data['data'] = collect { |e| e.respond_to?(:as_json) ? e.as_json : e }
345
+ end
284
346
  end
285
347
  end
286
348
 
287
349
  class ::Hash
350
+ include FIRM::Serializable::JSON::ContainerPatch
351
+
288
352
  class << self
289
- include FIRM::Serializable::JSON::HashClassPatch
353
+ # Create a new Hash instance from deserialized JSON data.
354
+ # @param [Hash] object deserialized JSON object
355
+ # @return [Hash] restored Hash instance
356
+ def json_create(object)
357
+ json_new(object) { |instance| instance.replace(object['data'].to_h) }
358
+ end
290
359
  end
360
+
291
361
  def as_json(*)
292
- {
293
- ::JSON.create_id => self.class.name,
294
- 'data' => collect { |k,v| [k.respond_to?(:as_json) ? k.as_json : k, v.respond_to?(:as_json) ? v.as_json : v] }
295
- }
362
+ build_json do |json_data|
363
+ json_data['data'] = collect { |k,v| [k.respond_to?(:as_json) ? k.as_json : k, v.respond_to?(:as_json) ? v.as_json : v] }
364
+ end
296
365
  end
297
366
  end
298
367
 
299
368
  class ::Set
369
+ include FIRM::Serializable::JSON::ContainerPatch
370
+
371
+ class << self
372
+ # Create a new Set instance from deserialized JSON data.
373
+ # @param [Hash] object deserialized JSON object
374
+ # @return [Set] restored Set instance
375
+ def json_create(object)
376
+ json_new(object) { |instance| instance.replace(object['a']) }
377
+ end
378
+ end
379
+
300
380
  def as_json(*)
301
- {
302
- JSON.create_id => self.class.name,
303
- 'a' => to_a.as_json,
304
- }
381
+ build_json do |json_data|
382
+ json_data['a'] = to_a.collect { |e| e.respond_to?(:as_json) ? e.as_json : e }
383
+ end
305
384
  end
306
385
  end
307
386
 
308
387
  class ::Struct
388
+ include FIRM::Serializable::JSON::ContainerPatch
389
+
309
390
  class << self
391
+ # Create a new Struct instance from deserialized JSON data.
392
+ # @param [Hash] object deserialized JSON object
393
+ # @return [Struct] restored Set instance
310
394
  def json_create(object)
311
- # deserializing (anchor) object or alias
312
- if object.has_key?('*id')
313
- if FIRM::Serializable::Aliasing.restored?(self, object['*id'])
314
- # resolving an already restored anchor for this alias
315
- FIRM::Serializable::Aliasing.resolve_anchor(self, object['*id'])
316
- else
317
- # in case of cyclic references JSON will restore aliases before the anchors
318
- # so in this case we allocate an instance here and register it as
319
- # the anchor; when the anchor is restored it will re-use this instance to restore
320
- # the properties
321
- FIRM::Serializable::Aliasing.restore_anchor(object['*id'], self.allocate)
322
- end
323
- else
324
- if object.has_key?('&id')
325
- anchor_id = object['&id'] # extract anchor id
326
- instance = if FIRM::Serializable::Aliasing.restored?(self, anchor_id)
327
- # in case of cyclic references an alias will already have restored the anchor instance
328
- # (default constructed); retrieve that instance here for deserialization of properties
329
- FIRM::Serializable::Aliasing.resolve_anchor(self, anchor_id)
330
- else
331
- # restore the anchor here with a newly instantiated instance
332
- FIRM::Serializable::Aliasing.restore_anchor(anchor_id, self.allocate)
333
- end
334
- instance.__send__(:initialize, *object['v'])
335
- instance
336
- else
337
- self.new(*object['v'])
395
+ json_new(object) do |instance|
396
+ values = object['v']
397
+ instance.members.each_with_index { |n, i| instance[n] = values[i] }
338
398
  end
339
- end
340
399
  end
341
400
  end
342
401
 
343
402
  def as_json(*)
344
- klass = self.class.name
345
- klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
346
- # {
347
- # JSON.create_id => klass,
348
- # 'v' => values.as_json,
349
- # }
350
- json_data = {
351
- ::JSON.create_id => klass
352
- }
353
- if (anchor = FIRM::Serializable::Aliasing.get_anchor(self))
354
- anchor_data = FIRM::Serializable::Aliasing.get_anchor_data(self)
355
- # retroactively insert the anchor in the anchored instance's serialization data
356
- anchor_data['&id'] = anchor unless anchor_data.has_key?('&id')
357
- json_data['*id'] = anchor
358
- else
359
- # register anchor object **before** serializing properties to properly handle cycling (bidirectional
360
- # references)
361
- FIRM::Serializable::Aliasing.register_anchor_object(self, json_data)
362
- json_data['v'] = values.as_json
403
+ self.class.name.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
404
+ build_json do |json_data|
405
+ json_data['v'] = values.collect { |e| e.respond_to?(:as_json) ? e.as_json : e }
363
406
  end
364
- json_data
365
407
  end
366
408
  end
367
409
 
368
410
  class ::OpenStruct
411
+ include FIRM::Serializable::JSON::ContainerPatch
412
+
413
+ class << self
414
+ # Create a new OpenStruct instance from deserialized JSON data.
415
+ # @param [Hash] object deserialized JSON object
416
+ # @return [OpenStruct] restored OpenStruct instance
417
+ def json_create(object)
418
+ json_new(object) { |instance| object['t'].each { |k,v| instance[k] = v } }
419
+ end
420
+ end
421
+
369
422
  def as_json(*)
370
- klass = self.class.name
371
- klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
372
- {
373
- JSON.create_id => klass,
374
- 't' => table.as_json,
375
- }
423
+ build_json do |json_data|
424
+ json_data['t'] = table.collect { |k,v| [k.respond_to?(:as_json) ? k.as_json : k, v.respond_to?(:as_json) ? v.as_json : v] }
425
+ end
376
426
  end
377
427
  end
378
428
 
@@ -56,6 +56,7 @@ module FIRM
56
56
  def create_type_node(xml)
57
57
  xml.add_child(Nokogiri::XML::Node.new(tag.to_s, xml.document))
58
58
  end
59
+ private :create_type_node
59
60
  def to_xml(_, _)
60
61
  raise Serializable::Exception, "Missing serialization method for #{klass} XML handler"
61
62
  end
@@ -65,6 +66,42 @@ module FIRM
65
66
  end
66
67
  private_constant :HandlerMethods
67
68
 
69
+ module AliasableHandler
70
+ def build_xml(xml, value, &block)
71
+ node = create_type_node(xml)
72
+ node['class'] = value.class.name
73
+ if (anchor = Serializable::Aliasing.get_anchor(value))
74
+ anchor_data = Serializable::Aliasing.get_anchor_data(value)
75
+ # retroactively insert the anchor in the anchored instance's serialization data
76
+ anchor_data['anchor'] = anchor unless anchor_data.has_attribute?('anchor')
77
+ node['alias'] = "#{anchor}"
78
+ else
79
+ # register anchor object **before** serializing properties to properly handle cycling (bidirectional
80
+ # references)
81
+ Serializable::Aliasing.register_anchor_object(value, node)
82
+ block.call(node)
83
+ end
84
+ xml
85
+ end
86
+ private :build_xml
87
+ def create_from_xml(xml, &block)
88
+ klass = ::Object.const_get(xml['class'])
89
+ if xml.has_attribute?('alias')
90
+ # deserializing alias
91
+ Serializable::Aliasing.resolve_anchor(klass, xml['alias'].to_i)
92
+ else
93
+ instance = klass.new
94
+ # in case this is an anchor restore the anchor instance before restoring the member values
95
+ # and afterwards initialize the instance with the restored member values
96
+ Serializable::Aliasing.restore_anchor(xml['anchor'].to_i, instance) if xml.has_attribute?('anchor')
97
+ block.call(instance)
98
+ instance
99
+ end
100
+ end
101
+ private :create_from_xml
102
+ end
103
+ private_constant :AliasableHandler
104
+
68
105
  def xml_handlers
69
106
  @xml_handlers ||= {}
70
107
  end
@@ -86,9 +123,10 @@ module FIRM
86
123
  end
87
124
  private :get_xml_handler
88
125
 
89
- def define_xml_handler(klass, tag=nil, &block)
126
+ def define_xml_handler(klass, tag=nil, aliasable: false, &block)
90
127
  hnd_klass = Class.new
91
128
  hnd_klass.singleton_class.include(HandlerMethods)
129
+ hnd_klass.singleton_class.include(AliasableHandler) if aliasable
92
130
  tag_code = if tag
93
131
  ::Symbol === tag ? ":#{tag}" : "'#{tag.to_s}'"
94
132
  else
@@ -179,16 +217,14 @@ module FIRM
179
217
  end
180
218
  end
181
219
 
182
- define_xml_handler(::Array) do
220
+ define_xml_handler(::Array, aliasable: true) do
183
221
  def to_xml(xml, value)
184
- node = create_type_node(xml)
185
- value.each do |v|
186
- Serializable::XML.to_xml(node, v)
187
- end
188
- xml
222
+ build_xml(xml, value) { |node| value.each { |v| Serializable::XML.to_xml(node, v) } }
189
223
  end
190
224
  def from_xml(xml)
191
- xml.elements.collect { |child| Serializable::XML.from_xml(child) }
225
+ create_from_xml(xml) do |instance|
226
+ instance.replace(xml.elements.collect { |child| Serializable::XML.from_xml(child) })
227
+ end
192
228
  end
193
229
  end
194
230
 
@@ -243,56 +279,35 @@ module FIRM
243
279
  end
244
280
  end
245
281
 
246
- define_xml_handler(::Hash) do
282
+ define_xml_handler(::Hash, aliasable: true) do
247
283
  def to_xml(xml, value)
248
- node = create_type_node(xml)
249
- value.each_pair do |k,v|
250
- pair = node.add_child(Nokogiri::XML::Node.new('P', node.document))
251
- Serializable::XML.to_xml(pair, k)
252
- Serializable::XML.to_xml(pair, v)
284
+ build_xml(xml, value) do |node|
285
+ value.each_pair do |k,v|
286
+ pair = node.add_child(Nokogiri::XML::Node.new('P', node.document))
287
+ Serializable::XML.to_xml(pair, k)
288
+ Serializable::XML.to_xml(pair, v)
289
+ end
253
290
  end
254
- xml
255
291
  end
256
292
  def from_xml(xml)
257
- xml.elements.inject({}) do |hash, pair|
258
- k, v = pair.elements
259
- hash[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
260
- hash
293
+ create_from_xml(xml) do |instance|
294
+ xml.elements.inject(instance) do |hash, pair|
295
+ k, v = pair.elements
296
+ instance[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
297
+ instance
298
+ end
261
299
  end
262
300
  end
263
301
  end
264
302
 
265
- define_xml_handler(::Struct) do
303
+ define_xml_handler(::Struct, aliasable: true) do
266
304
  def to_xml(xml, value)
267
- node = create_type_node(xml)
268
- node['class'] = value.class.name
269
- if (anchor = Serializable::Aliasing.get_anchor(value))
270
- anchor_data = Serializable::Aliasing.get_anchor_data(value)
271
- # retroactively insert the anchor in the anchored instance's serialization data
272
- anchor_data['anchor'] = anchor unless anchor_data.has_attribute?('anchor')
273
- node['alias'] = "#{anchor}"
274
- else
275
- # register anchor object **before** serializing properties to properly handle cycling (bidirectional
276
- # references)
277
- Serializable::Aliasing.register_anchor_object(value, node)
278
- value.each do |v|
279
- Serializable::XML.to_xml(node, v)
280
- end
281
- end
282
- xml
305
+ build_xml(xml, value) { |node| value.each { |v| Serializable::XML.to_xml(node, v) } }
283
306
  end
284
307
  def from_xml(xml)
285
- # deserializing alias
286
- klass = ::Object.const_get(xml['class'])
287
- if xml.has_attribute?('alias')
288
- Serializable::Aliasing.resolve_anchor(klass, xml['alias'].to_i)
289
- else
290
- instance = klass.allocate
291
- # in case this is an anchor restore the anchor instance before restoring the member values
292
- # and afterwards initialize the instance with the restored member values
293
- Serializable::Aliasing.restore_anchor(xml['anchor'].to_i, instance) if xml.has_attribute?('anchor')
294
- instance.__send__(:initialize, *xml.elements.collect { |child| Serializable::XML.from_xml(child) })
295
- instance
308
+ create_from_xml(xml) do |instance|
309
+ elems = xml.elements
310
+ instance.members.each_with_index { |n, i| instance[n] = Serializable::XML.from_xml(elems[i]) }
296
311
  end
297
312
  end
298
313
  end
@@ -408,34 +423,34 @@ module FIRM
408
423
  end
409
424
  end
410
425
 
411
- define_xml_handler(::Set) do
426
+ define_xml_handler(::Set, aliasable: true) do
412
427
  def to_xml(xml, value)
413
- node = create_type_node(xml)
414
- value.each do |v|
415
- Serializable::XML.to_xml(node, v)
416
- end
417
- xml
428
+ build_xml(xml, value) { |node| value.each { |v| Serializable::XML.to_xml(node, v) } }
418
429
  end
419
430
  def from_xml(xml)
420
- ::Set.new(xml.elements.collect { |child| Serializable::XML.from_xml(child) })
431
+ create_from_xml(xml) do |instance|
432
+ instance.replace(xml.elements.collect { |child| Serializable::XML.from_xml(child) })
433
+ end
421
434
  end
422
435
  end
423
436
 
424
- define_xml_handler(::OpenStruct) do
437
+ define_xml_handler(::OpenStruct, aliasable: true) do
425
438
  def to_xml(xml, value)
426
- node = create_type_node(xml)
427
- value.each_pair do |k,v|
428
- pair = node.add_child(Nokogiri::XML::Node.new('P', node.document))
429
- Serializable::XML.to_xml(pair, k)
430
- Serializable::XML.to_xml(pair, v)
439
+ build_xml(xml, value) do |node|
440
+ value.each_pair do |k,v|
441
+ pair = node.add_child(Nokogiri::XML::Node.new('P', node.document))
442
+ Serializable::XML.to_xml(pair, k)
443
+ Serializable::XML.to_xml(pair, v)
444
+ end
431
445
  end
432
- xml
433
446
  end
434
447
  def from_xml(xml)
435
- xml.elements.inject(::OpenStruct.new) do |hash, pair|
436
- k, v = pair.elements
437
- hash[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
438
- hash
448
+ create_from_xml(xml) do |instance|
449
+ xml.elements.inject(::OpenStruct.new) do |hash, pair|
450
+ k, v = pair.elements
451
+ instance[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
452
+ instance
453
+ end
439
454
  end
440
455
  end
441
456
  end
data/lib/firm/version.rb CHANGED
@@ -4,6 +4,6 @@
4
4
  module FIRM
5
5
 
6
6
  # FIRM version
7
- VERSION = "0.9.7"
7
+ VERSION = "0.9.8"
8
8
 
9
9
  end
@@ -770,12 +770,56 @@ module SerializerTestMixin
770
770
  struct = CyclicTest.new
771
771
  struct.list = [struct]
772
772
  obj_serial = struct.serialize
773
- struct_new = nil
774
- assert_nothing_raised { struct_new = FIRM.deserialize(obj_serial) }
773
+ struct_new = assert_nothing_raised { FIRM.deserialize(obj_serial) }
775
774
  assert_instance_of(CyclicTest, struct_new)
776
775
  assert_equal(struct_new.object_id, struct_new.list[0].object_id)
777
776
  end
778
777
 
778
+ def test_cyclic_core_containers
779
+
780
+ array = [1, 2, 3]
781
+ array << array
782
+ obj_serial = array.serialize
783
+ arr_new = assert_nothing_raised { FIRM.deserialize(obj_serial) }
784
+ assert_instance_of(::Array, arr_new)
785
+ assert_instance_of(::Array, arr_new.last)
786
+ assert_equal(arr_new.size, arr_new.last.size)
787
+ assert_equal(arr_new.object_id, arr_new.last.object_id)
788
+
789
+ hash = { one: 1, two: 2 }
790
+ hash[:self] = hash
791
+ obj_serial = hash.serialize
792
+ hash_new = assert_nothing_raised { FIRM.deserialize(obj_serial) }
793
+ assert_instance_of(::Hash, hash_new)
794
+ assert_instance_of(::Hash, hash_new[:self])
795
+ assert_equal(hash_new.size, hash_new[:self].size)
796
+ assert_equal(hash_new.object_id, hash_new[:self].object_id)
797
+
798
+ # the JRuby Psych implementation has a bug preventing cyclic reference support
799
+ # for Set objects (https://github.com/jruby/jruby/issues/8352)
800
+ unless defined? JRUBY_VERSION
801
+ set = ::Set.new([[1,2], {one: 1}])
802
+ set << set
803
+ obj_serial = set.serialize(pretty: true)
804
+ set_new = assert_nothing_raised { FIRM.deserialize(obj_serial) }
805
+ assert_instance_of(::Set, set_new)
806
+ assert_true(set_new.any? { |e| ::Array === e })
807
+ assert_true(set_new.any? { |e| ::Hash === e })
808
+ assert_true(set_new.any? { |e| ::Set === e && set_new.object_id == e.object_id })
809
+ end
810
+
811
+ ostruct = ::OpenStruct.new(one: 1, two: 2)
812
+ ostruct.me = ostruct
813
+ obj_serial = ostruct.serialize
814
+ ostruct_new = assert_nothing_raised { FIRM.deserialize(obj_serial) }
815
+ assert_instance_of(::OpenStruct, ostruct_new)
816
+ assert_equal(1, ostruct_new.one)
817
+ assert_equal(2, ostruct_new.two)
818
+ assert_instance_of(::OpenStruct, ostruct_new.me)
819
+ assert_equal(ostruct_new.object_id, ostruct_new.me.object_id)
820
+
821
+ end
822
+
779
823
  def test_nested_hash_with_complex_keys
780
824
  id_obj = Identifiable.new(:one)
781
825
  id_obj2 = Identifiable.new(:two)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 0.9.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Corino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-22 00:00:00.000000000 Z
11
+ date: 2024-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake