firm 0.9.1

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.
@@ -0,0 +1,544 @@
1
+ # FIRM::Serializer - shape serializer module
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+
5
+ require 'set'
6
+ require 'ostruct'
7
+ require 'date'
8
+
9
+ module FIRM
10
+
11
+ module Serializable
12
+
13
+ if ::Object.const_defined?(:Nokogiri)
14
+
15
+ module XML
16
+
17
+ class << self
18
+
19
+ TLS_STATE_KEY = :firm_xml_state.freeze
20
+ private_constant :TLS_STATE_KEY
21
+
22
+ def xml_state
23
+ ::Thread.current[TLS_STATE_KEY] ||= []
24
+ end
25
+ private :xml_state
26
+
27
+ def init_xml_load_state
28
+ xml_state.push({
29
+ allowed_classes: ::Set.new(Serializable.serializables.to_a.map(&:to_s))
30
+ })
31
+ end
32
+
33
+ def clear_xml_load_state
34
+ xml_state.pop
35
+ end
36
+
37
+ def xml_tag_allowed?(xml)
38
+ xml.name != 'Object' || (xml.has_attribute?('class') && xml_state.last[:allowed_classes].include?(xml['class']))
39
+ end
40
+ private :xml_tag_allowed?
41
+
42
+ class NullHandler
43
+ def initialize(tag)
44
+ @tag = tag
45
+ end
46
+ def to_xml(_, _)
47
+ raise Serializable::Exception, "Missing XML handler for #{@tag}"
48
+ end
49
+ def from_xml(_)
50
+ raise Serializable::Exception, "Missing XML handler for #{@tag}"
51
+ end
52
+ end
53
+ private_constant :NullHandler
54
+
55
+ module HandlerMethods
56
+ def create_type_node(xml)
57
+ xml.add_child(Nokogiri::XML::Node.new(tag.to_s, xml.document))
58
+ end
59
+ def to_xml(_, _)
60
+ raise Serializable::Exception, "Missing serialization method for #{klass} XML handler"
61
+ end
62
+ def from_xml(xml)
63
+ raise Serializable::Exception, "Missing serialization method for #{klass} XML handler"
64
+ end
65
+ end
66
+ private_constant :HandlerMethods
67
+
68
+ def xml_handlers
69
+ @xml_handlers ||= {}
70
+ end
71
+ private :xml_handlers
72
+
73
+ def register_xml_handler(handler)
74
+ raise RuntimeError, "Duplicate XML handler for tag #{handler.tag}" if xml_handlers.has_key?(handler.tag.to_s)
75
+ xml_handlers[handler.tag.to_s] = handler
76
+ end
77
+ private :register_xml_handler
78
+
79
+ def get_xml_handler(tag_or_value)
80
+ h = xml_handlers[tag_or_value.to_s]
81
+ unless h
82
+ tag_or_value = ::Object.const_get(tag_or_value.to_s) unless tag_or_value.is_a?(::Class)
83
+ h = xml_handlers.values.find { |hnd| hnd.klass > tag_or_value } || NullHandler.new(tag_or_value)
84
+ end
85
+ h
86
+ end
87
+ private :get_xml_handler
88
+
89
+ def define_xml_handler(klass, tag=nil, &block)
90
+ hnd_klass = Class.new
91
+ hnd_klass.singleton_class.include(HandlerMethods)
92
+ tag_code = if tag
93
+ ::Symbol === tag ? ":#{tag}" : "'#{tag.to_s}'"
94
+ else
95
+ 'klass'
96
+ end
97
+ hnd_klass.singleton_class.class_eval <<~__CODE
98
+ def klass; #{klass}; end
99
+ def tag; #{tag_code}; end
100
+ __CODE
101
+ hnd_klass.singleton_class.class_eval &block if block_given?
102
+ register_xml_handler(hnd_klass)
103
+ end
104
+
105
+ def to_xml(xml, value)
106
+ if Serializable === value
107
+ value.to_xml(xml)
108
+ else
109
+ hk = case value
110
+ when true, false
111
+ value.to_s
112
+ else
113
+ value ? value.class : 'nil'
114
+ end
115
+ get_xml_handler(hk).to_xml(xml, value)
116
+ end
117
+ end
118
+
119
+ def from_xml(xml)
120
+ raise Serializable::Exception, "Illegal XML tag #{xml.name}" unless xml_tag_allowed?(xml)
121
+ get_xml_handler(xml.name).from_xml(xml)
122
+ end
123
+
124
+ end
125
+
126
+ define_xml_handler(FIRM::Serializable, :Object) do
127
+ def to_xml(_, _)
128
+ raise Serializable::Exception, 'Unsupported Object serialization'
129
+ end
130
+ def from_xml(xml)
131
+ raise Serializable::Exception, 'Missing Serializable class name' unless xml.has_attribute?('class')
132
+ Object.const_get(xml['class']).from_xml(xml)
133
+ end
134
+ end
135
+
136
+ define_xml_handler(::NilClass, :nil) do
137
+ def to_xml(xml, _value)
138
+ create_type_node(xml)
139
+ xml
140
+ end
141
+ def from_xml(_xml)
142
+ nil
143
+ end
144
+ end
145
+
146
+ define_xml_handler(::TrueClass, :true) do
147
+ def to_xml(xml, _value)
148
+ create_type_node(xml)
149
+ xml
150
+ end
151
+ def from_xml(_xml)
152
+ true
153
+ end
154
+ end
155
+
156
+ define_xml_handler(::FalseClass, :false) do
157
+ def to_xml(xml, _value)
158
+ create_type_node(xml)
159
+ xml
160
+ end
161
+ def from_xml(_xml)
162
+ false
163
+ end
164
+ end
165
+
166
+ define_xml_handler(::Array) do
167
+ def to_xml(xml, value)
168
+ node = create_type_node(xml)
169
+ value.each do |v|
170
+ Serializable::XML.to_xml(node, v)
171
+ end
172
+ xml
173
+ end
174
+ def from_xml(xml)
175
+ xml.elements.collect { |child| Serializable::XML.from_xml(child) }
176
+ end
177
+ end
178
+
179
+ define_xml_handler(::String) do
180
+ def to_xml(xml, value)
181
+ create_type_node(xml).add_child(Nokogiri::XML::CDATA.new(xml.document, value))
182
+ xml
183
+ end
184
+ def from_xml(xml)
185
+ xml.content
186
+ end
187
+ end
188
+
189
+ define_xml_handler(::Symbol) do
190
+ def to_xml(xml, value)
191
+ create_type_node(xml).content = value.to_s
192
+ xml
193
+ end
194
+ def from_xml(xml)
195
+ xml.content.to_sym
196
+ end
197
+ end
198
+
199
+ define_xml_handler(::Integer) do
200
+ def to_xml(xml, value)
201
+ create_type_node(xml).content = value.to_s
202
+ xml
203
+ end
204
+ def from_xml(xml)
205
+ Integer(xml.content)
206
+ end
207
+ end
208
+
209
+ define_xml_handler(::Float) do
210
+ def to_xml(xml, value)
211
+ create_type_node(xml).add_child(Nokogiri::XML::CDATA.new(xml.document, value.to_s))
212
+ xml
213
+ end
214
+ def from_xml(xml)
215
+ case (s = xml.content)
216
+ when 'NaN' then :Float::NAN
217
+ when 'Infinity' then ::Float::INFINITY
218
+ when '-Infinity' then -::Float::INFINITY
219
+ else
220
+ Float(s)
221
+ end
222
+ end
223
+ end
224
+
225
+ define_xml_handler(::Hash) do
226
+ def to_xml(xml, value)
227
+ node = create_type_node(xml)
228
+ value.each_pair do |k,v|
229
+ pair = node.add_child(Nokogiri::XML::Node.new('P', node.document))
230
+ Serializable::XML.to_xml(pair, k)
231
+ Serializable::XML.to_xml(pair, v)
232
+ end
233
+ xml
234
+ end
235
+ def from_xml(xml)
236
+ xml.elements.inject({}) do |hash, pair|
237
+ k, v = pair.elements
238
+ hash[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
239
+ hash
240
+ end
241
+ end
242
+ end
243
+
244
+ define_xml_handler(::Struct) do
245
+ def to_xml(xml, value)
246
+ node = create_type_node(xml)
247
+ node['class'] = value.class.name
248
+ if (anchor = Serializable::Aliasing.get_anchor(value))
249
+ anchor_data = Serializable::Aliasing.get_anchor_data(value)
250
+ # retroactively insert the anchor in the anchored instance's serialization data
251
+ anchor_data['anchor'] = anchor unless anchor_data.has_attribute?('anchor')
252
+ node['alias'] = "#{anchor}"
253
+ else
254
+ # register anchor object **before** serializing properties to properly handle cycling (bidirectional
255
+ # references)
256
+ Serializable::Aliasing.register_anchor_object(value, node)
257
+ value.each do |v|
258
+ Serializable::XML.to_xml(node, v)
259
+ end
260
+ end
261
+ xml
262
+ end
263
+ def from_xml(xml)
264
+ # deserializing alias
265
+ klass = ::Object.const_get(xml['class'])
266
+ if xml.has_attribute?('alias')
267
+ Serializable::Aliasing.resolve_anchor(klass, xml['alias'].to_i)
268
+ else
269
+ instance = klass.allocate
270
+ # in case this is an anchor restore the anchor instance before restoring the member values
271
+ # and afterwards initialize the instance with the restored member values
272
+ Serializable::Aliasing.restore_anchor(xml['anchor'].to_i, instance) if xml.has_attribute?('anchor')
273
+ instance.__send__(:initialize, *xml.elements.collect { |child| Serializable::XML.from_xml(child) })
274
+ instance
275
+ end
276
+ end
277
+ end
278
+
279
+ define_xml_handler(::Rational) do
280
+ def to_xml(xml, value)
281
+ node = create_type_node(xml)
282
+ Serializable::XML.to_xml(node, value.numerator)
283
+ Serializable::XML.to_xml(node, value.denominator)
284
+ xml
285
+ end
286
+ def from_xml(xml)
287
+ Rational(*xml.elements.collect { |child| Serializable::XML.from_xml(child) })
288
+ end
289
+ end
290
+
291
+ define_xml_handler(::Complex) do
292
+ def to_xml(xml, value)
293
+ node = create_type_node(xml)
294
+ Serializable::XML.to_xml(node, value.real)
295
+ Serializable::XML.to_xml(node, value.imaginary)
296
+ xml
297
+ end
298
+ def from_xml(xml)
299
+ Complex(*xml.elements.collect { |child| Serializable::XML.from_xml(child) })
300
+ end
301
+ end
302
+
303
+ if ::Object.const_defined?(:BigDecimal)
304
+ define_xml_handler(::BigDecimal) do
305
+ def to_xml(xml, value)
306
+ create_type_node(xml).add_child(Nokogiri::XML::CDATA.new(xml.document, value._dump))
307
+ xml
308
+ end
309
+ def from_xml(xml)
310
+ ::BigDecimal._load(xml.content)
311
+ end
312
+ end
313
+ end
314
+
315
+ define_xml_handler(::Range) do
316
+ def to_xml(xml, value)
317
+ node = create_type_node(xml)
318
+ Serializable::XML.to_xml(node, value.begin)
319
+ Serializable::XML.to_xml(node, value.end)
320
+ Serializable::XML.to_xml(node, value.exclude_end?)
321
+ xml
322
+ end
323
+ def from_xml(xml)
324
+ ::Range.new(*xml.elements.collect { |child| Serializable::XML.from_xml(child) })
325
+ end
326
+ end
327
+
328
+ define_xml_handler(::Regexp) do
329
+ def to_xml(xml, value)
330
+ node = create_type_node(xml)
331
+ Serializable::XML.to_xml(node, value.source)
332
+ Serializable::XML.to_xml(node, value.options)
333
+ xml
334
+ end
335
+ def from_xml(xml)
336
+ ::Regexp.new(*xml.elements.collect { |child| Serializable::XML.from_xml(child) })
337
+ end
338
+ end
339
+
340
+ define_xml_handler(::Time) do
341
+ def to_xml(xml, value)
342
+ node = create_type_node(xml)
343
+ utc = value.getutc
344
+ Serializable::XML.to_xml(node, utc.tv_sec)
345
+ Serializable::XML.to_xml(node, utc.tv_nsec)
346
+ xml
347
+ end
348
+ def from_xml(xml)
349
+ ::Time.at(*xml.elements.collect { |child| Serializable::XML.from_xml(child) }, :nanosecond)
350
+ end
351
+ end
352
+
353
+ define_xml_handler(::Date) do
354
+ def to_xml(xml, value)
355
+ node = create_type_node(xml)
356
+ idt = value.italy
357
+ Serializable::XML.to_xml(node, idt.year)
358
+ Serializable::XML.to_xml(node, idt.month)
359
+ Serializable::XML.to_xml(node, idt.day)
360
+ xml
361
+ end
362
+ def from_xml(xml)
363
+ ::Date.new(*xml.elements.collect { |child| Serializable::XML.from_xml(child) }, ::Date::ITALY)
364
+ end
365
+ end
366
+
367
+ define_xml_handler(::DateTime) do
368
+ def to_xml(xml, value)
369
+ node = create_type_node(xml)
370
+ idt = value.italy
371
+ Serializable::XML.to_xml(node, idt.year)
372
+ Serializable::XML.to_xml(node, idt.month)
373
+ Serializable::XML.to_xml(node, idt.day)
374
+ Serializable::XML.to_xml(node, idt.hour)
375
+ Serializable::XML.to_xml(node, idt.min)
376
+ Serializable::XML.to_xml(node, idt.sec_fraction.to_f + idt.sec)
377
+ Serializable::XML.to_xml(node, idt.offset)
378
+ xml
379
+ end
380
+ def from_xml(xml)
381
+ ::DateTime.new(*xml.elements.collect { |child| Serializable::XML.from_xml(child) }, ::Date::ITALY)
382
+ end
383
+ end
384
+
385
+ define_xml_handler(::Set) do
386
+ def to_xml(xml, value)
387
+ node = create_type_node(xml)
388
+ value.each do |v|
389
+ Serializable::XML.to_xml(node, v)
390
+ end
391
+ xml
392
+ end
393
+ def from_xml(xml)
394
+ ::Set.new(xml.elements.collect { |child| Serializable::XML.from_xml(child) })
395
+ end
396
+ end
397
+
398
+ define_xml_handler(::OpenStruct) do
399
+ def to_xml(xml, value)
400
+ node = create_type_node(xml)
401
+ value.each_pair do |k,v|
402
+ pair = node.add_child(Nokogiri::XML::Node.new('P', node.document))
403
+ Serializable::XML.to_xml(pair, k)
404
+ Serializable::XML.to_xml(pair, v)
405
+ end
406
+ xml
407
+ end
408
+ def from_xml(xml)
409
+ xml.elements.inject(::OpenStruct.new) do |hash, pair|
410
+ k, v = pair.elements
411
+ hash[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
412
+ hash
413
+ end
414
+ end
415
+ end
416
+
417
+ class HashAdapter
418
+ def initialize(xml)
419
+ @xml = xml
420
+ end
421
+
422
+ def has_key?(id)
423
+ !!@xml.at_xpath(id.to_s)
424
+ end
425
+
426
+ def [](id)
427
+ node = @xml.at_xpath(id.to_s)
428
+ node = node ? node.first_element_child : nil
429
+ node ? Serializable::XML.from_xml(node) : nil
430
+ end
431
+
432
+ def []=(id, value)
433
+ Serializable::XML.to_xml(@xml.add_child(Nokogiri::XML::Node.new(id.to_s, @xml.document)), value)
434
+ end
435
+ end
436
+
437
+ def self.dump(obj, io=nil, pretty: false)
438
+ begin
439
+ # initialize anchor registry
440
+ Serializable::Aliasing.start_anchor_object_registry
441
+ # generate XML document
442
+ xml = to_xml(Nokogiri::XML::Document.new, obj)
443
+ opts = pretty ? { indent: 2 } : { save_with: 0 }
444
+ if io || io.respond_to?(:write)
445
+ xml.write_xml_to(io, opts)
446
+ io
447
+ else
448
+ xml.to_xml(opts)
449
+ end
450
+ ensure
451
+ # reset anchor registry
452
+ Serializable::Aliasing.clear_anchor_object_registry
453
+ end
454
+ end
455
+
456
+ def self.load(source)
457
+ xml = Nokogiri::XML(source)
458
+ return nil unless xml
459
+ begin
460
+ # initialize alias anchor restoration map
461
+ Serializable::Aliasing.start_anchor_references
462
+ # initialize XML loader state
463
+ Serializable::XML.init_xml_load_state
464
+ # load from xml doc
465
+ xml.root ? Serializable::XML.from_xml(xml.root) : nil
466
+ ensure
467
+ # reset XML loader state
468
+ Serializable::XML.clear_xml_load_state
469
+ # reset alias anchor restoration map
470
+ Serializable::Aliasing.clear_anchor_references
471
+ end
472
+ end
473
+
474
+ # extend serialization class methods
475
+ module SerializeClassMethods
476
+
477
+ def from_xml(xml)
478
+ data = XML::HashAdapter.new(xml)
479
+ # deserializing alias
480
+ if xml.has_attribute?('alias')
481
+ Serializable::Aliasing.resolve_anchor(self, xml['alias'].to_i)
482
+ else
483
+ instance = self.allocate
484
+ Serializable::Aliasing.restore_anchor(xml['anchor'].to_i, instance) if xml.has_attribute?('anchor')
485
+ instance.__send__(:init_from_serialized, data)
486
+ .__send__(:from_serialized, data)
487
+ .__send__(:finalize_from_serialized)
488
+ end
489
+ end
490
+
491
+ end
492
+
493
+ # extend instance serialization methods
494
+ module SerializeInstanceMethods
495
+
496
+ def to_xml(xml)
497
+ node = xml.add_child(Nokogiri::XML::Node.new('Object', xml.document))
498
+ node['class'] = self.class.name
499
+ if (anchor = Serializable::Aliasing.get_anchor(self))
500
+ anchor_data = Serializable::Aliasing.get_anchor_data(self)
501
+ # retroactively insert the anchor in the anchored instance's serialization data
502
+ anchor_data['anchor'] = anchor unless anchor_data.has_attribute?('anchor')
503
+ node['alias'] = "#{anchor}"
504
+ else
505
+ # register anchor object **before** serializing properties to properly handle cycling (bidirectional
506
+ # references)
507
+ Serializable::Aliasing.register_anchor_object(self, node)
508
+ for_serialize(XML::HashAdapter.new(node))
509
+ end
510
+ xml
511
+ end
512
+
513
+ end
514
+
515
+ end
516
+
517
+ # extend serialization class methods
518
+ module SerializeClassMethods
519
+
520
+ include XML::SerializeClassMethods
521
+
522
+ end
523
+
524
+ # extend instance serialization methods
525
+ module SerializeInstanceMethods
526
+
527
+ include XML::SerializeInstanceMethods
528
+
529
+ end
530
+
531
+ class ID
532
+ include XML::SerializeInstanceMethods
533
+ class << self
534
+ include XML::SerializeClassMethods
535
+ end
536
+ end
537
+
538
+ register(:xml, XML)
539
+
540
+ end
541
+
542
+ end
543
+
544
+ end
@@ -0,0 +1,118 @@
1
+ # FIRM::Serializer - shape serializer module
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+
5
+ require 'yaml'
6
+ require 'date'
7
+ require 'set'
8
+ require 'ostruct'
9
+
10
+ module FIRM
11
+
12
+ module Serializable
13
+
14
+ module YAML
15
+
16
+ class << self
17
+ def serializables
18
+ list = [::Date, ::DateTime, ::Range, ::Rational, ::Complex, ::Regexp, ::Struct, ::Symbol, ::Time, ::Set, ::OpenStruct]
19
+ list.push(::BigDecimal) if ::Object.const_defined?(:BigDecimal)
20
+ list
21
+ end
22
+ end
23
+
24
+ module YamlSerializePatch
25
+
26
+ ALLOWED_ALIASES = [Serializable::ID]
27
+
28
+ if ::RUBY_VERSION >= '3.1.0'
29
+ def revive(klass, node)
30
+ if FIRM::Serializable > klass
31
+ s = register(node, klass.allocate)
32
+ s.__send__(:init_from_serialized, data = revive_hash({}, node, true))
33
+ init_with(s, data, node)
34
+ else
35
+ super
36
+ end
37
+ end
38
+ else
39
+ def revive(klass, node)
40
+ if FIRM::Serializable > klass
41
+ s = register(node, klass.allocate)
42
+ s.__send__(:init_from_serialized, data = revive_hash({}, node))
43
+ init_with(s, data, node)
44
+ else
45
+ super
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ class RestrictedRelaxed < ::YAML::ClassLoader
52
+ def initialize(classes)
53
+ @classes = classes
54
+ @allow_struct = @classes.include?('Struct')
55
+ super()
56
+ end
57
+
58
+ private
59
+
60
+ def find(klassname)
61
+ if @classes.include?(klassname)
62
+ super
63
+ elsif @allow_struct && ::Struct > super
64
+ @cache[klassname]
65
+ else
66
+ raise ::YAML::DisallowedClass.new('load', klassname)
67
+ end
68
+ end
69
+ end
70
+
71
+ def self.dump(obj, io=nil, **)
72
+ ::YAML.dump(obj, io)
73
+ end
74
+
75
+ def self.load(source)
76
+ result = ::YAML.parse(source, filename: nil)
77
+ return nil unless result
78
+ allowed_classes =(YAML.serializables + Serializable.serializables.to_a).map(&:to_s)
79
+ class_loader = RestrictedRelaxed.new(allowed_classes)
80
+ scanner = ::YAML::ScalarScanner.new(class_loader)
81
+ visitor = ::YAML::Visitors::ToRuby.new(scanner, class_loader)
82
+ visitor.extend(YamlSerializePatch)
83
+ visitor.accept result
84
+ end
85
+
86
+ end
87
+
88
+ # extend instance serialization methods
89
+ module SerializeInstanceMethods
90
+
91
+ def encode_with(coder)
92
+ for_serialize(coder)
93
+ end
94
+
95
+ def init_with(coder)
96
+ from_serialized(coder.map)
97
+ finalize_from_serialized
98
+ end
99
+
100
+ end
101
+
102
+ class ID
103
+
104
+ def encode_with(coder)
105
+ for_serialize(coder)
106
+ end
107
+
108
+ def init_with(_coder)
109
+ # noop
110
+ end
111
+
112
+ end
113
+
114
+ register(:yaml, YAML)
115
+
116
+ end
117
+
118
+ end
@@ -0,0 +1,9 @@
1
+ # FIRM - multiple output ruby object serializer<br>
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+ module FIRM
5
+
6
+ # FIRM version
7
+ VERSION = "0.9.1"
8
+
9
+ end
data/lib/firm.rb ADDED
@@ -0,0 +1,5 @@
1
+ # FIRM - multiple output ruby object serializer
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+ require 'firm/version'
5
+ require 'firm/serializable'