firm 0.9.7 → 0.9.8

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: 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