shale 0.8.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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