shale 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Mapping
5
+ # Mapping for key/value serialization formats (Hash/JSON/YAML)
6
+ #
7
+ # @api private
8
+ class KeyValue
9
+ # Return keys mapping hash
10
+ #
11
+ # @return [Hash]
12
+ #
13
+ # @api private
14
+ attr_reader :keys
15
+
16
+ # Initialize instance
17
+ #
18
+ # @api private
19
+ def initialize
20
+ @keys = {}
21
+ end
22
+
23
+ # Map key to attribute
24
+ #
25
+ # @param [String] key Document's key
26
+ # @param [Symbol] to Object's attribute
27
+ #
28
+ # @api private
29
+ def map(key, to:)
30
+ @keys[key] = to
31
+ end
32
+
33
+ # @api private
34
+ def initialize_dup(other)
35
+ @keys = other.instance_variable_get('@keys').dup
36
+ super
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Mapping
5
+ class Xml
6
+ # Return elements mapping hash
7
+ #
8
+ # @return [Hash]
9
+ #
10
+ # @api private
11
+ attr_reader :elements
12
+
13
+ # Return attributes mapping hash
14
+ #
15
+ # @return [Hash]
16
+ #
17
+ # @api private
18
+ attr_reader :attributes
19
+
20
+ # Return content mapping
21
+ #
22
+ # @return [Symbol]
23
+ #
24
+ # @api private
25
+ attr_reader :content
26
+
27
+ # Initialize instance
28
+ #
29
+ # @api private
30
+ def initialize
31
+ @elements = {}
32
+ @attributes = {}
33
+ @content = nil
34
+ @root = ''
35
+ end
36
+
37
+ # Map element to attribute
38
+ #
39
+ # @param [String] element Document's element
40
+ # @param [Symbol] to Object's attribute
41
+ #
42
+ # @api private
43
+ def map_element(element, to:)
44
+ @elements[element] = to
45
+ end
46
+
47
+ # Map document's attribute to object's attribute
48
+ #
49
+ # @param [String] attribute Document's attribute
50
+ # @param [Symbol] to Object's attribute
51
+ #
52
+ # @api private
53
+ def map_attribute(attribute, to:)
54
+ @attributes[attribute] = to
55
+ end
56
+
57
+ # Map document's content to object's attribute
58
+ #
59
+ # @param [Symbol] to Object's attribute
60
+ #
61
+ # @api private
62
+ def map_content(to:)
63
+ @content = to
64
+ end
65
+
66
+ # Name document's element
67
+ #
68
+ # @param [String, nil] value Document's element name
69
+ #
70
+ # @return [Stirng, nil]
71
+ #
72
+ # @api private
73
+ def root(value = nil)
74
+ value.nil? ? @root : @root = value
75
+ end
76
+
77
+ # @api private
78
+ def initialize_dup(other)
79
+ @elements = other.instance_variable_get('@elements').dup
80
+ @attributes = other.instance_variable_get('@attributes').dup
81
+ @content = other.instance_variable_get('@content').dup
82
+ @root = other.instance_variable_get('@root').dup
83
+ super
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Type
5
+ # Base class for all types
6
+ #
7
+ # @example
8
+ # class MyType < Shale::Type::Base
9
+ # ... overwrite methods as needed
10
+ # end
11
+ #
12
+ # @api public
13
+ class Base
14
+ class << self
15
+ # Cast raw value to a type. Base form just returns whatever it receives
16
+ #
17
+ # @param [any] value Value to cast
18
+ #
19
+ # @return [any]
20
+ #
21
+ # @api private
22
+ def cast(value)
23
+ value
24
+ end
25
+
26
+ # Extract value from Hash document
27
+ #
28
+ # @param [any] value
29
+ #
30
+ # @return [any]
31
+ #
32
+ # @api private
33
+ def out_of_hash(value)
34
+ value
35
+ end
36
+
37
+ # Convert value to form accepted by Hash document
38
+ #
39
+ # @param [any] value
40
+ #
41
+ # @return [any]
42
+ #
43
+ # @api private
44
+ def as_hash(value)
45
+ value
46
+ end
47
+
48
+ # Extract value from JSON document
49
+ #
50
+ # @param [any] value
51
+ #
52
+ # @return [any]
53
+ #
54
+ # @api private
55
+ def out_of_json(value)
56
+ value
57
+ end
58
+
59
+ # Convert value to form accepted by JSON document
60
+ #
61
+ # @param [any] value
62
+ #
63
+ # @return [any]
64
+ #
65
+ # @api private
66
+ def as_json(value)
67
+ value
68
+ end
69
+
70
+ # Extract value from YAML document
71
+ #
72
+ # @param [any] value
73
+ #
74
+ # @return [any]
75
+ #
76
+ # @api private
77
+ def out_of_yaml(value)
78
+ value
79
+ end
80
+
81
+ # Convert value to form accepted by YAML document
82
+ #
83
+ # @param [any] value
84
+ #
85
+ # @return [any]
86
+ #
87
+ # @api private
88
+ def as_yaml(value)
89
+ value
90
+ end
91
+
92
+ # Extract value from XML document
93
+ #
94
+ # @param [Shale::Adapter::<XML adapter>::Node] value
95
+ #
96
+ # @return [String]
97
+ #
98
+ # @api private
99
+ def out_of_xml(node)
100
+ node.text
101
+ end
102
+
103
+ # Convert value to form accepted by XML document
104
+ #
105
+ # @param [#to_s] value Value to convert to XML
106
+ # @param [String] name Name of the element
107
+ # @param [Shale::Adapter::<XML adapter>::Document] doc Document
108
+ #
109
+ # @api private
110
+ def as_xml(value, name, doc)
111
+ element = doc.create_element(name)
112
+ doc.add_text(element, value.to_s)
113
+ element
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Shale
6
+ module Type
7
+ # Cast value to Boolean
8
+ #
9
+ # @api public
10
+ class Boolean < Base
11
+ FALSE_VALUES = [
12
+ false,
13
+ 0,
14
+ '0',
15
+ 'f',
16
+ 'F',
17
+ 'false',
18
+ 'FALSE',
19
+ 'off',
20
+ 'OFF',
21
+ ].freeze
22
+
23
+ # @param [any] value Value to cast
24
+ #
25
+ # @return [Boolean, nil]
26
+ #
27
+ # @api private
28
+ def self.cast(value)
29
+ !FALSE_VALUES.include?(value) unless value.nil?
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,427 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Shale
6
+ module Type
7
+ # Build complex object. Don't use it directly.
8
+ # It serves as a base type class for @see Shale::Mapper
9
+ #
10
+ # @api private
11
+ class Complex < Base
12
+ class << self
13
+ # Convert Hash to Object
14
+ #
15
+ # @param [Hash] hash Hash to convert
16
+ #
17
+ # @return [Shale::Mapper]
18
+ #
19
+ # @api public
20
+ def out_of_hash(hash)
21
+ instance = new
22
+
23
+ hash.each do |key, value|
24
+ mapping = hash_mapping.keys[key]
25
+ next unless mapping
26
+
27
+ attribute = attributes[mapping]
28
+ next unless attribute
29
+
30
+ if value.nil?
31
+ instance.public_send("#{attribute.name}=", nil)
32
+ next
33
+ end
34
+
35
+ if attribute.collection?
36
+ [*value].each do |val|
37
+ val = val ? attribute.type.out_of_hash(val) : val
38
+ instance.public_send(attribute.name) << attribute.type.cast(val)
39
+ end
40
+ else
41
+ instance.public_send("#{attribute.name}=", attribute.type.out_of_hash(value))
42
+ end
43
+ end
44
+
45
+ instance
46
+ end
47
+
48
+ alias from_hash out_of_hash
49
+
50
+ # Convert Object to Hash
51
+ #
52
+ # @param [Shale::Type::Base] instance Object to convert
53
+ #
54
+ # @return [Hash]
55
+ #
56
+ # @api public
57
+ def as_hash(instance)
58
+ hash = {}
59
+
60
+ instance.class.hash_mapping.keys.each do |key, attr|
61
+ attribute = instance.class.attributes[attr]
62
+ next unless attribute
63
+
64
+ value = instance.public_send(attribute.name)
65
+
66
+ if value.nil?
67
+ hash[key] = nil
68
+ next
69
+ end
70
+
71
+ if attribute.collection?
72
+ hash[key] = [*value].map { |v| v ? attribute.type.as_hash(v) : v }
73
+ else
74
+ hash[key] = attribute.type.as_hash(value)
75
+ end
76
+ end
77
+
78
+ hash
79
+ end
80
+
81
+ alias to_hash as_hash
82
+
83
+ # Convert JSON document to Object
84
+ #
85
+ # @param [Hash] hash JSON document to convert
86
+ #
87
+ # @return [Shale::Mapper]
88
+ #
89
+ # @api public
90
+ def out_of_json(hash)
91
+ instance = new
92
+
93
+ hash.each do |key, value|
94
+ mapping = json_mapping.keys[key]
95
+ next unless mapping
96
+
97
+ attribute = attributes[mapping]
98
+ next unless attribute
99
+
100
+ if value.nil?
101
+ instance.public_send("#{attribute.name}=", nil)
102
+ next
103
+ end
104
+
105
+ if attribute.collection?
106
+ [*value].each do |val|
107
+ val = val ? attribute.type.out_of_json(val) : val
108
+ instance.public_send(attribute.name) << attribute.type.cast(val)
109
+ end
110
+ else
111
+ instance.public_send("#{attribute.name}=", attribute.type.out_of_json(value))
112
+ end
113
+ end
114
+
115
+ instance
116
+ end
117
+
118
+ # Convert JSON to Object
119
+ #
120
+ # @param [String] json JSON to convert
121
+ #
122
+ # @return [Shale::Mapper]
123
+ #
124
+ # @api public
125
+ def from_json(json)
126
+ out_of_json(Shale.json_adapter.load(json))
127
+ end
128
+
129
+ # Convert Object to JSON document
130
+ #
131
+ # @param [Shale::Type::Base] instance Object to convert
132
+ #
133
+ # @return [Hash]
134
+ #
135
+ # @api public
136
+ def as_json(instance)
137
+ hash = {}
138
+
139
+ instance.class.json_mapping.keys.each do |key, attr|
140
+ attribute = instance.class.attributes[attr]
141
+ next unless attribute
142
+
143
+ value = instance.public_send(attribute.name)
144
+
145
+ if value.nil?
146
+ hash[key] = nil
147
+ next
148
+ end
149
+
150
+ if attribute.collection?
151
+ hash[key] = [*value].map { |v| v ? attribute.type.as_json(v) : v }
152
+ else
153
+ hash[key] = attribute.type.as_json(value)
154
+ end
155
+ end
156
+
157
+ hash
158
+ end
159
+
160
+ # Convert Object to JSON
161
+ #
162
+ # @param [Shale::Type::Base] instance Object to convert
163
+ #
164
+ # @return [String]
165
+ #
166
+ # @api public
167
+ def to_json(instance)
168
+ Shale.json_adapter.dump(as_json(instance))
169
+ end
170
+
171
+ # Convert YAML document to Object
172
+ #
173
+ # @param [Hash] hash YAML document to convert
174
+ #
175
+ # @return [Shale::Mapper]
176
+ #
177
+ # @api public
178
+ def out_of_yaml(hash)
179
+ instance = new
180
+
181
+ hash.each do |key, value|
182
+ mapping = yaml_mapping.keys[key]
183
+ next unless mapping
184
+
185
+ attribute = attributes[mapping]
186
+ next unless attribute
187
+
188
+ if value.nil?
189
+ instance.public_send("#{attribute.name}=", nil)
190
+ next
191
+ end
192
+
193
+ if attribute.collection?
194
+ [*value].each do |val|
195
+ val = val ? attribute.type.out_of_yaml(val) : val
196
+ instance.public_send(attribute.name) << attribute.type.cast(val)
197
+ end
198
+ else
199
+ instance.public_send("#{attribute.name}=", attribute.type.out_of_yaml(value))
200
+ end
201
+ end
202
+
203
+ instance
204
+ end
205
+
206
+ # Convert YAML to Object
207
+ #
208
+ # @param [String] yaml YAML to convert
209
+ #
210
+ # @return [Shale::Mapper]
211
+ #
212
+ # @api public
213
+ def from_yaml(yaml)
214
+ out_of_yaml(Shale.yaml_adapter.load(yaml))
215
+ end
216
+
217
+ # Convert Object to YAML document
218
+ #
219
+ # @param [Shale::Type::Base] instance Object to convert
220
+ #
221
+ # @return [Hash]
222
+ #
223
+ # @api public
224
+ def as_yaml(instance)
225
+ hash = {}
226
+
227
+ instance.class.yaml_mapping.keys.each do |key, attr|
228
+ attribute = instance.class.attributes[attr]
229
+ next unless attribute
230
+
231
+ value = instance.public_send(attribute.name)
232
+
233
+ if value.nil?
234
+ hash[key] = nil
235
+ next
236
+ end
237
+
238
+ if attribute.collection?
239
+ hash[key] = [*value].map { |v| v ? attribute.type.as_yaml(v) : v }
240
+ else
241
+ hash[key] = attribute.type.as_yaml(value)
242
+ end
243
+ end
244
+
245
+ hash
246
+ end
247
+
248
+ # Convert Object to YAML
249
+ #
250
+ # @param [Shale::Type::Base] instance Object to convert
251
+ #
252
+ # @return [String]
253
+ #
254
+ # @api public
255
+ def to_yaml(instance)
256
+ Shale.yaml_adapter.dump(as_yaml(instance))
257
+ end
258
+
259
+ # Convert XML document to Object
260
+ #
261
+ # @param [Shale::Adapter::<XML adapter>::Node] xml XML to convert
262
+ #
263
+ # @return [Shale::Mapper]
264
+ #
265
+ # @api public
266
+ def out_of_xml(element)
267
+ instance = new
268
+
269
+ element.attributes.each do |key, value|
270
+ mapping = xml_mapping.attributes[key.to_s]
271
+ next unless mapping
272
+
273
+ attribute = attributes[mapping]
274
+ next unless attribute
275
+
276
+ if attribute.collection?
277
+ instance.public_send(attribute.name) << attribute.type.cast(value)
278
+ else
279
+ instance.public_send("#{attribute.name}=", value)
280
+ end
281
+ end
282
+
283
+ if xml_mapping.content
284
+ attribute = attributes[xml_mapping.content]
285
+
286
+ if attribute
287
+ instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(element))
288
+ end
289
+ end
290
+
291
+ element.children.each do |node|
292
+ mapping = xml_mapping.elements[node.name]
293
+ next unless mapping
294
+
295
+ attribute = attributes[mapping]
296
+ next unless attribute
297
+
298
+ if attribute.collection?
299
+ value = attribute.type.out_of_xml(node)
300
+ instance.public_send(attribute.name) << attribute.type.cast(value)
301
+ else
302
+ instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(node))
303
+ end
304
+ end
305
+
306
+ instance
307
+ end
308
+
309
+ # Convert XML to Object
310
+ #
311
+ # @param [String] xml XML to convert
312
+ #
313
+ # @return [Shale::Mapper]
314
+ #
315
+ # @api public
316
+ def from_xml(xml)
317
+ out_of_xml(Shale.xml_adapter.load(xml))
318
+ end
319
+
320
+ # Convert Object to XML document
321
+ #
322
+ # @param [Shale::Type::Base] instance Object to convert
323
+ # @param [String, nil] node_name XML node name
324
+ # @param [Shale::Adapter::<xml adapter>::Document, nil] doc Object to convert
325
+ #
326
+ # @return [::REXML::Document, ::Nokogiri::Document, ::Ox::Document]
327
+ #
328
+ # @api public
329
+ def as_xml(instance, node_name = nil, doc = nil)
330
+ unless doc
331
+ doc = Shale.xml_adapter.create_document
332
+ doc.add_element(doc.doc, as_xml(instance, instance.class.xml_mapping.root, doc))
333
+ return doc.doc
334
+ end
335
+
336
+ element = doc.create_element(node_name)
337
+
338
+ xml_mapping.attributes.each do |xml_attr, obj_attr|
339
+ attribute = instance.class.attributes[obj_attr]
340
+ next unless attribute
341
+
342
+ value = instance.public_send(attribute.name)
343
+
344
+ if value && !value.empty?
345
+ doc.add_attribute(element, xml_attr, value)
346
+ end
347
+ end
348
+
349
+ if xml_mapping.content
350
+ attribute = instance.class.attributes[xml_mapping.content]
351
+
352
+ if attribute
353
+ value = instance.public_send(attribute.name)
354
+ doc.add_text(element, value.to_s) if value
355
+ end
356
+ end
357
+
358
+ xml_mapping.elements.each do |xml_name, obj_attr|
359
+ attribute = instance.class.attributes[obj_attr]
360
+ next unless attribute
361
+
362
+ value = instance.public_send(attribute.name)
363
+ next if value.nil?
364
+
365
+ if attribute.collection?
366
+ [*value].each do |v|
367
+ next if v.nil?
368
+ doc.add_element(element, attribute.type.as_xml(v, xml_name, doc))
369
+ end
370
+ else
371
+ doc.add_element(element, attribute.type.as_xml(value, xml_name, doc))
372
+ end
373
+ end
374
+
375
+ element
376
+ end
377
+
378
+ # Convert Object to XML
379
+ #
380
+ # @param [Shale::Type::Base] instance Object to convert
381
+ #
382
+ # @return [String]
383
+ #
384
+ # @api public
385
+ def to_xml(instance)
386
+ Shale.xml_adapter.dump(as_xml(instance))
387
+ end
388
+ end
389
+
390
+ # Convert Object to Hash
391
+ #
392
+ # @return [Hash]
393
+ #
394
+ # @api public
395
+ def to_hash
396
+ self.class.to_hash(self)
397
+ end
398
+
399
+ # Convert Object to JSON
400
+ #
401
+ # @return [String]
402
+ #
403
+ # @api public
404
+ def to_json
405
+ self.class.to_json(self)
406
+ end
407
+
408
+ # Convert Object to YAML
409
+ #
410
+ # @return [String]
411
+ #
412
+ # @api public
413
+ def to_yaml
414
+ self.class.to_yaml(self)
415
+ end
416
+
417
+ # Convert Object to XML
418
+ #
419
+ # @return [String]
420
+ #
421
+ # @api public
422
+ def to_xml
423
+ self.class.to_xml(self)
424
+ end
425
+ end
426
+ end
427
+ end