shale 0.1.0

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