shale 0.8.0 → 1.0.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.
@@ -9,9 +9,14 @@ module Shale
9
9
  class Document
10
10
  # Initialize object
11
11
  #
12
+ # @param [String, nil] version
13
+ #
12
14
  # @api private
13
- def initialize
14
- @doc = ::Nokogiri::XML::Document.new
15
+ def initialize(version = nil)
16
+ ver = nil
17
+ ver = version if version.is_a?(String)
18
+
19
+ @doc = ::Nokogiri::XML::Document.new(ver)
15
20
  @namespaces = {}
16
21
  end
17
22
 
@@ -37,12 +37,13 @@ module Shale
37
37
  #
38
38
  # @param [::Nokogiri::XML::Document] doc Nokogiri document
39
39
  # @param [true, false] pretty
40
- # @param [true, false] declaration
40
+ # @param [true, false, String] declaration
41
+ # @param [true, false, String] encoding
41
42
  #
42
43
  # @return [String]
43
44
  #
44
45
  # @api private
45
- def self.dump(doc, pretty: false, declaration: false)
46
+ def self.dump(doc, pretty: false, declaration: false, encoding: false)
46
47
  save_with = ::Nokogiri::XML::Node::SaveOptions::AS_XML
47
48
 
48
49
  if pretty
@@ -53,6 +54,10 @@ module Shale
53
54
  save_with |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
54
55
  end
55
56
 
57
+ if encoding
58
+ doc.encoding = encoding == true ? 'UTF-8' : encoding
59
+ end
60
+
56
61
  result = doc.to_xml(save_with: save_with)
57
62
 
58
63
  unless pretty
@@ -64,9 +69,11 @@ module Shale
64
69
 
65
70
  # Create Shale::Adapter::Nokogiri::Document instance
66
71
  #
72
+ # @param [true, false, String, nil] declaration
73
+ #
67
74
  # @api private
68
- def self.create_document
69
- Document.new
75
+ def self.create_document(version = nil)
76
+ Document.new(version)
70
77
  end
71
78
  end
72
79
  end
@@ -31,12 +31,13 @@ module Shale
31
31
  #
32
32
  # @param [::Ox::Document, ::Ox::Element] doc Ox document
33
33
  # @param [true, false] pretty
34
- # @param [true, false] declaration
34
+ # @param [true, false, String] declaration
35
+ # @param [true, false, String] encoding
35
36
  #
36
37
  # @return [String]
37
38
  #
38
39
  # @api private
39
- def self.dump(doc, pretty: false, declaration: false)
40
+ def self.dump(doc, pretty: false, declaration: false, encoding: false)
40
41
  opts = { indent: -1, with_xml: false }
41
42
 
42
43
  if pretty
@@ -44,7 +45,12 @@ module Shale
44
45
  end
45
46
 
46
47
  if declaration
47
- doc[:version] = '1.0'
48
+ doc[:version] = declaration == true ? '1.0' : declaration
49
+
50
+ if encoding
51
+ doc[:encoding] = encoding == true ? 'UTF-8' : encoding
52
+ end
53
+
48
54
  opts[:with_xml] = true
49
55
  end
50
56
 
@@ -54,7 +60,7 @@ module Shale
54
60
  # Create Shale::Adapter::Ox::Document instance
55
61
  #
56
62
  # @api private
57
- def self.create_document
63
+ def self.create_document(_version = nil)
58
64
  Document.new
59
65
  end
60
66
  end
@@ -32,14 +32,28 @@ module Shale
32
32
  #
33
33
  # @param [::REXML::Document] doc REXML document
34
34
  # @param [true, false] pretty
35
- # @param [true, false] declaration
35
+ # @param [true, false, String] declaration
36
+ # @param [true, false, String] encoding
36
37
  #
37
38
  # @return [String]
38
39
  #
39
40
  # @api private
40
- def self.dump(doc, pretty: false, declaration: false)
41
+ def self.dump(doc, pretty: false, declaration: false, encoding: false)
41
42
  if declaration
42
- doc.add(::REXML::XMLDecl.new)
43
+ ver = nil
44
+ enc = nil
45
+
46
+ if declaration.is_a?(String)
47
+ ver = declaration
48
+ end
49
+
50
+ if encoding == true
51
+ enc = 'UTF-8'
52
+ else
53
+ enc = encoding || nil
54
+ end
55
+
56
+ doc.add(::REXML::XMLDecl.new(ver, enc))
43
57
  end
44
58
 
45
59
  io = StringIO.new
@@ -58,7 +72,7 @@ module Shale
58
72
  # Create Shale::Adapter::REXML::Document instance
59
73
  #
60
74
  # @api private
61
- def self.create_document
75
+ def self.create_document(_version = nil)
62
76
  Document.new
63
77
  end
64
78
  end
data/lib/shale/error.rb CHANGED
@@ -53,10 +53,16 @@ module Shale
53
53
  end
54
54
  end
55
55
 
56
+ # Shale base error class
57
+ #
58
+ # @api private
59
+ class ShaleError < StandardError
60
+ end
61
+
56
62
  # Error for trying to assign not callable object as an attribute's default
57
63
  #
58
64
  # @api private
59
- class DefaultNotCallableError < StandardError
65
+ class DefaultNotCallableError < ShaleError
60
66
  # Initialize error object
61
67
  #
62
68
  # @param [String] record
@@ -71,36 +77,42 @@ module Shale
71
77
  # Error for passing incorrect model type
72
78
  #
73
79
  # @api private
74
- class IncorrectModelError < StandardError
80
+ class IncorrectModelError < ShaleError
75
81
  end
76
82
 
77
83
  # Error for passing incorrect arguments to map functions
78
84
  #
79
85
  # @api private
80
- class IncorrectMappingArgumentsError < StandardError
86
+ class IncorrectMappingArgumentsError < ShaleError
87
+ end
88
+
89
+ # Error for using incorrect type
90
+ #
91
+ # @api private
92
+ class NotAShaleMapperError < ShaleError
81
93
  end
82
94
 
83
- # Error for passing incorrect arguments to schema generation function
95
+ # Raised when receiver attribute is not defined
84
96
  #
85
97
  # @api private
86
- class NotAShaleMapperError < StandardError
98
+ class AttributeNotDefinedError < ShaleError
87
99
  end
88
100
 
89
101
  # Schema compilation error
90
102
  #
91
103
  # @api private
92
- class SchemaError < StandardError
104
+ class SchemaError < ShaleError
93
105
  end
94
106
 
95
107
  # Parsing error
96
108
  #
97
109
  # @api private
98
- class ParseError < StandardError
110
+ class ParseError < ShaleError
99
111
  end
100
112
 
101
113
  # Adapter error
102
114
  #
103
115
  # @api private
104
- class AdapterError < StandardError
116
+ class AdapterError < ShaleError
105
117
  end
106
118
  end
data/lib/shale/mapper.rb CHANGED
@@ -49,6 +49,7 @@ module Shale
49
49
  @json_mapping = Mapping::Dict.new
50
50
  @yaml_mapping = Mapping::Dict.new
51
51
  @toml_mapping = Mapping::Dict.new
52
+ @csv_mapping = Mapping::Dict.new(render_nil_default: true)
52
53
  @xml_mapping = Mapping::Xml.new
53
54
 
54
55
  class << self
@@ -87,6 +88,13 @@ module Shale
87
88
  # @api public
88
89
  attr_reader :toml_mapping
89
90
 
91
+ # Return CSV mapping object
92
+ #
93
+ # @return [Shale::Mapping::Dict]
94
+ #
95
+ # @api public
96
+ attr_reader :csv_mapping
97
+
90
98
  # Return XML mapping object
91
99
  #
92
100
  # @return [Shale::Mapping::XML]
@@ -109,12 +117,14 @@ module Shale
109
117
  subclass.instance_variable_set('@__json_mapping_init', @json_mapping.dup)
110
118
  subclass.instance_variable_set('@__yaml_mapping_init', @yaml_mapping.dup)
111
119
  subclass.instance_variable_set('@__toml_mapping_init', @toml_mapping.dup)
120
+ subclass.instance_variable_set('@__csv_mapping_init', @csv_mapping.dup)
112
121
  subclass.instance_variable_set('@__xml_mapping_init', @xml_mapping.dup)
113
122
 
114
123
  subclass.instance_variable_set('@hash_mapping', @hash_mapping.dup)
115
124
  subclass.instance_variable_set('@json_mapping', @json_mapping.dup)
116
125
  subclass.instance_variable_set('@yaml_mapping', @yaml_mapping.dup)
117
126
  subclass.instance_variable_set('@toml_mapping', @toml_mapping.dup)
127
+ subclass.instance_variable_set('@csv_mapping', @csv_mapping.dup)
118
128
 
119
129
  xml_mapping = @xml_mapping.dup
120
130
  xml_mapping.root(Utils.underscore(subclass.name || ''))
@@ -141,7 +151,7 @@ module Shale
141
151
  # @raise [DefaultNotCallableError] when attribute's default is not callable
142
152
  #
143
153
  # @example
144
- # calss Person < Shale::Mapper
154
+ # class Person < Shale::Mapper
145
155
  # attribute :first_name, Shale::Type::String
146
156
  # attribute :last_name, Shale::Type::String
147
157
  # attribute :age, Shale::Type::Integer, default: -> { 1 }
@@ -173,6 +183,7 @@ module Shale
173
183
  @json_mapping.map(name.to_s, to: name) unless @json_mapping.finalized?
174
184
  @yaml_mapping.map(name.to_s, to: name) unless @yaml_mapping.finalized?
175
185
  @toml_mapping.map(name.to_s, to: name) unless @toml_mapping.finalized?
186
+ @csv_mapping.map(name.to_s, to: name) unless @csv_mapping.finalized?
176
187
  @xml_mapping.map_element(name.to_s, to: name) unless @xml_mapping.finalized?
177
188
 
178
189
  @attributes_module.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
@@ -189,7 +200,7 @@ module Shale
189
200
  # @param [Proc] block
190
201
  #
191
202
  # @example
192
- # calss Person < Shale::Mapper
203
+ # class Person < Shale::Mapper
193
204
  # attribute :first_name, Shale::Type::String
194
205
  # attribute :last_name, Shale::Type::String
195
206
  # attribute :age, Shale::Type::Integer
@@ -213,7 +224,7 @@ module Shale
213
224
  # @param [Proc] block
214
225
  #
215
226
  # @example
216
- # calss Person < Shale::Mapper
227
+ # class Person < Shale::Mapper
217
228
  # attribute :first_name, Shale::Type::String
218
229
  # attribute :last_name, Shale::Type::String
219
230
  # attribute :age, Shale::Type::Integer
@@ -237,7 +248,7 @@ module Shale
237
248
  # @param [Proc] block
238
249
  #
239
250
  # @example
240
- # calss Person < Shale::Mapper
251
+ # class Person < Shale::Mapper
241
252
  # attribute :first_name, Shale::Type::String
242
253
  # attribute :last_name, Shale::Type::String
243
254
  # attribute :age, Shale::Type::Integer
@@ -261,7 +272,7 @@ module Shale
261
272
  # @param [Proc] block
262
273
  #
263
274
  # @example
264
- # calss Person < Shale::Mapper
275
+ # class Person < Shale::Mapper
265
276
  # attribute :first_name, Shale::Type::String
266
277
  # attribute :last_name, Shale::Type::String
267
278
  # attribute :age, Shale::Type::Integer
@@ -280,12 +291,36 @@ module Shale
280
291
  @toml_mapping.instance_eval(&block)
281
292
  end
282
293
 
294
+ # Define CSV mapping
295
+ #
296
+ # @param [Proc] block
297
+ #
298
+ # @example
299
+ # class Person < Shale::Mapper
300
+ # attribute :first_name, Shale::Type::String
301
+ # attribute :last_name, Shale::Type::String
302
+ # attribute :age, Shale::Type::Integer
303
+ #
304
+ # csv do
305
+ # map 'first_name', to: :first_name
306
+ # map 'last_name', to: :last_name
307
+ # map 'age', to: :age
308
+ # end
309
+ # end
310
+ #
311
+ # @api public
312
+ def csv(&block)
313
+ @csv_mapping = @__csv_mapping_init.dup
314
+ @csv_mapping.finalize!
315
+ @csv_mapping.instance_eval(&block)
316
+ end
317
+
283
318
  # Define XML mapping
284
319
  #
285
320
  # @param [Proc] block
286
321
  #
287
322
  # @example
288
- # calss Person < Shale::Mapper
323
+ # class Person < Shale::Mapper
289
324
  # attribute :first_name, Shale::Type::String
290
325
  # attribute :last_name, Shale::Type::String
291
326
  # attribute :age, Shale::Type::Integer
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Mapping
5
+ # Class for handling attribute delegation
6
+ #
7
+ # @api private
8
+ class Delegates
9
+ # Class representing individual delegation
10
+ #
11
+ # @api private
12
+ class Delegate
13
+ # Return receiver_attribute
14
+ #
15
+ # @return [Shale::Attribute]
16
+ #
17
+ # @api private
18
+ attr_reader :receiver_attribute
19
+
20
+ # Return attribute setter on a delegate
21
+ #
22
+ # @return [String]
23
+ #
24
+ # @api private
25
+ attr_reader :setter
26
+
27
+ # Return value to set on a delegate
28
+ #
29
+ # @return [any]
30
+ #
31
+ # @api private
32
+ attr_reader :value
33
+
34
+ # Initialize instance
35
+ #
36
+ # @param [Shale::Attribute] receiver_attribute
37
+ # @param [String] setter
38
+ # @param [any] value
39
+ #
40
+ # @api private
41
+ def initialize(receiver_attribute, setter, value)
42
+ @receiver_attribute = receiver_attribute
43
+ @setter = setter
44
+ @value = value
45
+ end
46
+ end
47
+
48
+ # Initialize instance
49
+ #
50
+ # @api private
51
+ def initialize
52
+ @delegates = []
53
+ end
54
+
55
+ # Add single value to delegate
56
+ #
57
+ # @param [Shale::Attribute] receiver_attribute
58
+ # @param [String] setter
59
+ # @param [any] value
60
+ #
61
+ # @api private
62
+ def add(receiver_attribute, setter, value)
63
+ @delegates << Delegate.new(receiver_attribute, setter, value)
64
+ end
65
+
66
+ # Add collection to delegate
67
+ #
68
+ # @param [Shale::Attribute] receiver_attribute
69
+ # @param [String] setter
70
+ # @param [any] value
71
+ #
72
+ # @api private
73
+ def add_collection(receiver_attribute, setter, value)
74
+ delegate = @delegates.find do |e|
75
+ e.receiver_attribute == receiver_attribute && e.setter == setter
76
+ end
77
+
78
+ if delegate
79
+ delegate.value << value
80
+ else
81
+ @delegates << Delegate.new(receiver_attribute, setter, [value])
82
+ end
83
+ end
84
+
85
+ # Iterate over delegates and yield a block
86
+ #
87
+ # @param [Proc] block
88
+ #
89
+ # @api private
90
+ def each(&block)
91
+ @delegates.each(&block)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -21,6 +21,13 @@ module Shale
21
21
  # @api private
22
22
  attr_reader :attribute
23
23
 
24
+ # Return receiver name
25
+ #
26
+ # @return [Symbol]
27
+ #
28
+ # @api private
29
+ attr_reader :receiver
30
+
24
31
  # Return method symbol
25
32
  #
26
33
  # @return [Symbol]
@@ -46,14 +53,16 @@ module Shale
46
53
  #
47
54
  # @param [String] name
48
55
  # @param [Symbol, nil] attribute
56
+ # @param [Symbol, nil] receiver
49
57
  # @param [Hash, nil] methods
50
58
  # @param [String, nil] group
51
59
  # @param [true, false] render_nil
52
60
  #
53
61
  # @api private
54
- def initialize(name:, attribute:, methods:, group:, render_nil:)
62
+ def initialize(name:, attribute:, receiver:, methods:, group:, render_nil:)
55
63
  @name = name
56
64
  @attribute = attribute
65
+ @receiver = receiver
57
66
  @group = group
58
67
  @render_nil = render_nil
59
68
 
@@ -26,7 +26,8 @@ module Shale
26
26
  # Initialize instance
27
27
  #
28
28
  # @param [String] name
29
- # @param [Symbol, String] attribute
29
+ # @param [Symbol, nil] attribute
30
+ # @param [Symbol, nil] receiver
30
31
  # @param [Hash, nil] methods
31
32
  # @param [String, nil] group
32
33
  # @param [Shale::Mapping::XmlNamespace] namespace
@@ -34,10 +35,20 @@ module Shale
34
35
  # @param [true, false] render_nil
35
36
  #
36
37
  # @api private
37
- def initialize(name:, attribute:, methods:, group:, namespace:, cdata:, render_nil:)
38
+ def initialize(
39
+ name:,
40
+ attribute:,
41
+ receiver:,
42
+ methods:,
43
+ group:,
44
+ namespace:,
45
+ cdata:,
46
+ render_nil:
47
+ )
38
48
  super(
39
49
  name: name,
40
50
  attribute: attribute,
51
+ receiver: receiver,
41
52
  methods: methods,
42
53
  group: group,
43
54
  render_nil: render_nil
@@ -5,22 +5,32 @@ require_relative 'dict_group'
5
5
 
6
6
  module Shale
7
7
  module Mapping
8
- # Mapping for dictionary serialization formats (Hash/JSON/YAML)
8
+ # Mapping for dictionary serialization formats (Hash/JSON/YAML/TOML/CSV)
9
9
  #
10
10
  # @api private
11
11
  class Dict < DictBase
12
12
  # Map key to attribute
13
13
  #
14
14
  # @param [String] key Document's key
15
- # @param [Symbol, nil] to Object's attribute
15
+ # @param [Symbol, nil] to
16
+ # @param [Symbol, nil] receiver
16
17
  # @param [Hash, nil] using
17
- # @param [true, false] render_nil
18
+ # @param [true, false, nil] render_nil
18
19
  #
19
20
  # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
20
21
  #
21
22
  # @api private
22
- def map(key, to: nil, using: nil, render_nil: false)
23
- super(key, to: to, using: using, render_nil: render_nil)
23
+ def map(key, to: nil, receiver: nil, using: nil, render_nil: nil)
24
+ super(key, to: to, receiver: receiver, using: using, render_nil: render_nil)
25
+ end
26
+
27
+ # Set render_nil default
28
+ #
29
+ # @param [true, false] val
30
+ #
31
+ # @api private
32
+ def render_nil(val)
33
+ @render_nil_default = val
24
34
  end
25
35
 
26
36
  # Map group of keys to mapping methods
@@ -5,7 +5,7 @@ require_relative 'validator'
5
5
 
6
6
  module Shale
7
7
  module Mapping
8
- # Base class for Mapping dictionary serialization formats (Hash/JSON/YAML)
8
+ # Base class for Mapping dictionary serialization formats (Hash/JSON/YAML/TOML/CSV)
9
9
  #
10
10
  # @api private
11
11
  class DictBase
@@ -18,31 +18,37 @@ module Shale
18
18
 
19
19
  # Initialize instance
20
20
  #
21
+ # @param [true, false] render_nil_default
22
+ #
21
23
  # @api private
22
- def initialize
24
+ def initialize(render_nil_default: false)
23
25
  @keys = {}
24
26
  @finalized = false
27
+ @render_nil_default = render_nil_default
25
28
  end
26
29
 
27
30
  # Map key to attribute
28
31
  #
29
32
  # @param [String] key
30
33
  # @param [Symbol, nil] to
34
+ # @param [Symbol, nil] receiver
31
35
  # @param [Hash, nil] using
32
36
  # @param [String, nil] group
33
- # @param [true, false] render_nil
37
+ # @param [true, false, nil] render_nil
34
38
  #
35
39
  # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
36
40
  #
37
41
  # @api private
38
- def map(key, to: nil, using: nil, group: nil, render_nil: false)
39
- Validator.validate_arguments(key, to, using)
42
+ def map(key, to: nil, receiver: nil, using: nil, group: nil, render_nil: nil)
43
+ Validator.validate_arguments(key, to, receiver, using)
44
+
40
45
  @keys[key] = Descriptor::Dict.new(
41
46
  name: key,
42
47
  attribute: to,
48
+ receiver: receiver,
43
49
  methods: using,
44
50
  group: group,
45
- render_nil: render_nil
51
+ render_nil: render_nil.nil? ? @render_nil_default : render_nil
46
52
  )
47
53
  end
48
54
 
@@ -4,7 +4,7 @@ require_relative 'dict_base'
4
4
 
5
5
  module Shale
6
6
  module Mapping
7
- # Group for dictionary serialization formats (Hash/JSON/YAML)
7
+ # Group for dictionary serialization formats (Hash/JSON/YAML/TOML/CSV)
8
8
  #
9
9
  # @api private
10
10
  class DictGroup < DictBase
@@ -8,18 +8,25 @@ module Shale
8
8
  # Validate correctness of argument passed to map functions
9
9
  #
10
10
  # @param [String] key
11
- # @param [Symbol] to
12
- # @param [Hash] using
11
+ # @param [Symbol, nil] to
12
+ # @param [Symbol, nil] receiver
13
+ # @param [Hash, nil] using
13
14
  #
14
15
  # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
15
16
  #
16
17
  # @api private
17
- def self.validate_arguments(key, to, using)
18
+ def self.validate_arguments(key, to, receiver, using)
18
19
  if to.nil? && using.nil?
19
20
  msg = ":to or :using argument is required for mapping '#{key}'"
20
21
  raise IncorrectMappingArgumentsError, msg
21
22
  end
22
23
 
24
+ if to.nil? && !receiver.nil?
25
+ msg = ":receiver argument for mapping '#{key}' " \
26
+ 'can only be used together with :to argument'
27
+ raise IncorrectMappingArgumentsError, msg
28
+ end
29
+
23
30
  if !using.nil? && (using[:from].nil? || using[:to].nil?)
24
31
  msg = ":using argument for mapping '#{key}' requires :to and :from keys"
25
32
  raise IncorrectMappingArgumentsError, msg