shale 0.7.1 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 468fc4f3fa1ccd796958748eea653aaa9aec0dbf223c2e5486ddba00350d79fc
4
- data.tar.gz: 4b78746e97bc6fd080b8a6566be2534c3a1c05017acacabd3935f7e2ea81b097
3
+ metadata.gz: 76c0022b3d2d42be143362c84c6697628781acdce7694fb9bc0f305d3f854964
4
+ data.tar.gz: 0b0b1b060eab81ebfae80afa3c31c809280b66ebd77e413c5908c4bbd09a081a
5
5
  SHA512:
6
- metadata.gz: 776a30c22e95f8c85e150b64ec7df09e89aeb4e86fb2f6f0fcec67a438b13b73926c3d5419b8194f457a2f09fc8eb64bf491e8b1ea5c5d6957e1e1b5cfab9549
7
- data.tar.gz: 0d236bde4f3b5aaf9b3ea5a5b8380b3861b24f5d0b310430329c68d010c368890dedbd7f6fad8649b71cd443362c8c6272ebb9c7a234998fa546ecb99e728f44
6
+ metadata.gz: 43bc9188d1f07c5c367a8ed94e2a3da6b878a7b7b86773d7898697abbd3937d4de6ae0471f9362c72e8a6be2cc5e5100a5206b2f2cc7155ff2ff8784953ef702
7
+ data.tar.gz: 0e610703cc73809a78007785962d9a322e515b88ab143a55adfeaa1a0da5961f14d8768a3dec4ad797070bbab165c238123702cbc671f522f86e2934968ff78b
data/CHANGELOG.md CHANGED
@@ -1,9 +1,19 @@
1
- ## [0.7.1] - [2022-08-12]
1
+ ## [0.8.0] - 2022-08-30
2
+
3
+ ### Added
4
+ - Allow to group mappings using `group` block
5
+ - Bring back Ruby 2.6 support
6
+
7
+ ### Changed
8
+ - Use anonymous module for attributes definition.
9
+ It allows to override accessors and `super` works as expected.
10
+
11
+ ## [0.7.1] - 2022-08-12
2
12
 
3
13
  ### Fixed
4
14
  - Fix broken handling of Date and Time types
5
15
 
6
- ## [0.7.0] - [2022-08-09]
16
+ ## [0.7.0] - 2022-08-09
7
17
 
8
18
  ### Added
9
19
  - `only: []` and `except: []` options that allow to controll what attributes are rendered/parsed
data/README.md CHANGED
@@ -17,7 +17,7 @@ Documentation with interactive examples is available at [Shale website](https://
17
17
 
18
18
  ## Installation
19
19
 
20
- Shale supports Ruby (MRI) 2.7+
20
+ Shale supports Ruby (MRI) 2.6+
21
21
 
22
22
  Add this line to your application's Gemfile:
23
23
 
@@ -729,6 +729,55 @@ end
729
729
  Person.new(password: 'secret').to_json(context: current_user)
730
730
  ```
731
731
 
732
+ If you want to work on multiple elements at a time you can group them using `group` block:
733
+
734
+ ```ruby
735
+ class Person < Shale::Mapper
736
+ attribute :name, Shale::Type::String
737
+
738
+ json do
739
+ group from: :name_from_json, to: :name_to_json do
740
+ map 'first_name'
741
+ map 'last_name'
742
+ end
743
+ end
744
+
745
+ xml do
746
+ group from: :name_from_xml, to: :name_to_xml do
747
+ map_content
748
+ map_element 'first_name'
749
+ map_attribute 'last_name'
750
+ end
751
+ end
752
+
753
+ def name_from_json(model, value)
754
+ model.name = "#{value['first_name']} #{value['last_name']}"
755
+ end
756
+
757
+ def name_to_json(model, doc)
758
+ doc['first_name'] = model.name.split(' ')[0]
759
+ doc['last_name'] = model.name.split(' ')[1]
760
+ end
761
+
762
+ def name_from_xml(model, value)
763
+ # value => { content: ..., attributes: {}, elements: {} }
764
+ end
765
+
766
+ def name_to_xml(model, element, doc)
767
+ # ...
768
+ end
769
+ end
770
+
771
+ Person.from_json(<<~DATA)
772
+ {
773
+ "first_name": "John",
774
+ "last_name": "Doe"
775
+ }
776
+ DATA
777
+
778
+ # => #<Person:0x00007f9bc3086d60 @name="John Doe">
779
+ ```
780
+
732
781
  ### Additional options
733
782
 
734
783
  You can control which attributes to render and parse by
data/lib/shale/mapper.rb CHANGED
@@ -98,6 +98,10 @@ module Shale
98
98
  def inherited(subclass)
99
99
  super
100
100
 
101
+ attributes_module = Module.new
102
+ subclass.instance_variable_set('@attributes_module', attributes_module)
103
+ subclass.include(attributes_module)
104
+
101
105
  subclass.instance_variable_set('@model', subclass)
102
106
  subclass.instance_variable_set('@attributes', @attributes.dup)
103
107
 
@@ -171,7 +175,7 @@ module Shale
171
175
  @toml_mapping.map(name.to_s, to: name) unless @toml_mapping.finalized?
172
176
  @xml_mapping.map_element(name.to_s, to: name) unless @xml_mapping.finalized?
173
177
 
174
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
178
+ @attributes_module.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
175
179
  attr_reader :#{name}
176
180
 
177
181
  def #{name}=(val)
@@ -35,17 +35,26 @@ module Shale
35
35
  # @api private
36
36
  attr_reader :method_to
37
37
 
38
+ # Return group name
39
+ #
40
+ # @return [String]
41
+ #
42
+ # @api private
43
+ attr_reader :group
44
+
38
45
  # Initialize instance
39
46
  #
40
47
  # @param [String] name
41
48
  # @param [Symbol, nil] attribute
42
49
  # @param [Hash, nil] methods
50
+ # @param [String, nil] group
43
51
  # @param [true, false] render_nil
44
52
  #
45
53
  # @api private
46
- def initialize(name:, attribute:, methods:, render_nil:)
54
+ def initialize(name:, attribute:, methods:, group:, render_nil:)
47
55
  @name = name
48
56
  @attribute = attribute
57
+ @group = group
49
58
  @render_nil = render_nil
50
59
 
51
60
  if methods
@@ -28,13 +28,21 @@ module Shale
28
28
  # @param [String] name
29
29
  # @param [Symbol, String] attribute
30
30
  # @param [Hash, nil] methods
31
+ # @param [String, nil] group
31
32
  # @param [Shale::Mapping::XmlNamespace] namespace
32
33
  # @param [true, false] cdata
33
34
  # @param [true, false] render_nil
34
35
  #
35
36
  # @api private
36
- def initialize(name:, attribute:, methods:, namespace:, cdata:, render_nil:)
37
- super(name: name, attribute: attribute, methods: methods, render_nil: render_nil)
37
+ def initialize(name:, attribute:, methods:, group:, namespace:, cdata:, render_nil:)
38
+ super(
39
+ name: name,
40
+ attribute: attribute,
41
+ methods: methods,
42
+ group: group,
43
+ render_nil: render_nil
44
+ )
45
+
38
46
  @namespace = namespace
39
47
  @cdata = cdata
40
48
  end
@@ -47,6 +55,15 @@ module Shale
47
55
  def prefixed_name
48
56
  [namespace.prefix, name].compact.join(':')
49
57
  end
58
+
59
+ # Return name with XML namespace
60
+ #
61
+ # @return [String]
62
+ #
63
+ # @api private
64
+ def namespaced_name
65
+ [namespace.name, name].compact.join(':')
66
+ end
50
67
  end
51
68
  end
52
69
  end
@@ -1,30 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'descriptor/dict'
4
- require_relative 'validator'
3
+ require_relative 'dict_base'
4
+ require_relative 'dict_group'
5
5
 
6
6
  module Shale
7
7
  module Mapping
8
8
  # Mapping for dictionary serialization formats (Hash/JSON/YAML)
9
9
  #
10
10
  # @api private
11
- class Dict
12
- # Return keys mapping hash
13
- #
14
- # @return [Hash]
15
- #
16
- # @api private
17
- attr_reader :keys
18
-
19
- # Initialize instance
20
- #
21
- # @api private
22
- def initialize
23
- super
24
- @keys = {}
25
- @finalized = false
26
- end
27
-
11
+ class Dict < DictBase
28
12
  # Map key to attribute
29
13
  #
30
14
  # @param [String] key Document's key
@@ -36,36 +20,20 @@ module Shale
36
20
  #
37
21
  # @api private
38
22
  def map(key, to: nil, using: nil, render_nil: false)
39
- Validator.validate_arguments(key, to, using)
40
- @keys[key] = Descriptor::Dict.new(
41
- name: key,
42
- attribute: to,
43
- methods: using,
44
- render_nil: render_nil
45
- )
23
+ super(key, to: to, using: using, render_nil: render_nil)
46
24
  end
47
25
 
48
- # Set the "finalized" instance variable to true
26
+ # Map group of keys to mapping methods
49
27
  #
50
- # @api private
51
- def finalize!
52
- @finalized = true
53
- end
54
-
55
- # Query the "finalized" instance variable
56
- #
57
- # @return [truem false]
28
+ # @param [Symbol] from
29
+ # @param [Symbol] to
30
+ # @param [Proc] block
58
31
  #
59
32
  # @api private
60
- def finalized?
61
- @finalized
62
- end
63
-
64
- # @api private
65
- def initialize_dup(other)
66
- @keys = other.instance_variable_get('@keys').dup
67
- @finalized = false
68
- super
33
+ def group(from:, to:, &block)
34
+ group = DictGroup.new(from, to)
35
+ group.instance_eval(&block)
36
+ @keys.merge!(group.keys)
69
37
  end
70
38
  end
71
39
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'descriptor/dict'
4
+ require_relative 'validator'
5
+
6
+ module Shale
7
+ module Mapping
8
+ # Base class for Mapping dictionary serialization formats (Hash/JSON/YAML)
9
+ #
10
+ # @api private
11
+ class DictBase
12
+ # Return keys mapping hash
13
+ #
14
+ # @return [Hash]
15
+ #
16
+ # @api private
17
+ attr_reader :keys
18
+
19
+ # Initialize instance
20
+ #
21
+ # @api private
22
+ def initialize
23
+ @keys = {}
24
+ @finalized = false
25
+ end
26
+
27
+ # Map key to attribute
28
+ #
29
+ # @param [String] key
30
+ # @param [Symbol, nil] to
31
+ # @param [Hash, nil] using
32
+ # @param [String, nil] group
33
+ # @param [true, false] render_nil
34
+ #
35
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
36
+ #
37
+ # @api private
38
+ def map(key, to: nil, using: nil, group: nil, render_nil: false)
39
+ Validator.validate_arguments(key, to, using)
40
+ @keys[key] = Descriptor::Dict.new(
41
+ name: key,
42
+ attribute: to,
43
+ methods: using,
44
+ group: group,
45
+ render_nil: render_nil
46
+ )
47
+ end
48
+
49
+ # Set the "finalized" instance variable to true
50
+ #
51
+ # @api private
52
+ def finalize!
53
+ @finalized = true
54
+ end
55
+
56
+ # Query the "finalized" instance variable
57
+ #
58
+ # @return [truem false]
59
+ #
60
+ # @api private
61
+ def finalized?
62
+ @finalized
63
+ end
64
+
65
+ # @api private
66
+ def initialize_dup(other)
67
+ @keys = other.instance_variable_get('@keys').dup
68
+ @finalized = false
69
+ super
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dict_base'
4
+
5
+ module Shale
6
+ module Mapping
7
+ # Group for dictionary serialization formats (Hash/JSON/YAML)
8
+ #
9
+ # @api private
10
+ class DictGroup < DictBase
11
+ # Return name of the group
12
+ #
13
+ # @return [String]
14
+ #
15
+ # @api private
16
+ attr_reader :name
17
+
18
+ # Initialize instance
19
+ #
20
+ # @param [Symbol] from
21
+ # @param [Symbol] to
22
+ #
23
+ # @api private
24
+ def initialize(from, to)
25
+ super()
26
+ @from = from
27
+ @to = to
28
+ @name = "group_#{hash}"
29
+ end
30
+
31
+ # Map key to attribute
32
+ #
33
+ # @param [String] key
34
+ #
35
+ # @api private
36
+ def map(key)
37
+ super(key, using: { from: @from, to: @to }, group: @name)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Mapping
5
+ module Group
6
+ # Dict group descriptor
7
+ #
8
+ # @api private
9
+ class Dict
10
+ # Return method_from
11
+ #
12
+ # @return [Symbol]
13
+ #
14
+ # @api private
15
+ attr_reader :method_from
16
+
17
+ # Return method_to
18
+ #
19
+ # @return [Symbol]
20
+ #
21
+ # @api private
22
+ attr_reader :method_to
23
+
24
+ # Return dict hash
25
+ #
26
+ # @return [Hash]
27
+ #
28
+ # @api private
29
+ attr_reader :dict
30
+
31
+ # Initialize instance
32
+ #
33
+ # @param [Symbol] method_from
34
+ # @param [Symbol] method_to
35
+ #
36
+ # @api private
37
+ def initialize(method_from, method_to)
38
+ @method_from = method_from
39
+ @method_to = method_to
40
+ @dict = {}
41
+ end
42
+
43
+ # Add key-value pair to a group
44
+ #
45
+ # @param [String] key
46
+ # @param [any] value
47
+ #
48
+ # @api private
49
+ def add(key, value)
50
+ @dict[key] = value
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dict'
4
+
5
+ module Shale
6
+ module Mapping
7
+ module Group
8
+ # Class representing mapping group for JSON/YAML/TOML
9
+ #
10
+ # @api private
11
+ class DictGrouping
12
+ # Initialize instance
13
+ #
14
+ # @api private
15
+ def initialize
16
+ @groups = {}
17
+ end
18
+
19
+ # Add a value to a group
20
+ #
21
+ # @param [Shale::Mapping::Descriptor::Dict] mapping
22
+ # @param [any] value
23
+ #
24
+ # @api private
25
+ def add(mapping, value)
26
+ group = @groups[mapping.group] ||= Dict.new(mapping.method_from, mapping.method_to)
27
+ group.add(mapping.name, value)
28
+ end
29
+
30
+ # Iterate over groups
31
+ #
32
+ # @param [Proc] block
33
+ #
34
+ # @api private
35
+ def each(&block)
36
+ @groups.values.each(&block)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dict'
4
+
5
+ module Shale
6
+ module Mapping
7
+ module Group
8
+ # Xml group descriptor
9
+ #
10
+ # @api private
11
+ class Xml < Dict
12
+ # Initialize instance
13
+ #
14
+ # @param [Symbol] method_from
15
+ # @param [Symbol] method_to
16
+ #
17
+ # @api private
18
+ def initialize(method_from, method_to)
19
+ super(method_from, method_to)
20
+ @dict = { content: nil, attributes: {}, elements: {} }
21
+ end
22
+
23
+ # Add key-value pair to a group
24
+ #
25
+ # @param [Symbol] kind
26
+ # @param [String] key
27
+ # @param [any] value
28
+ #
29
+ # @api private
30
+ def add(kind, key, value)
31
+ case kind
32
+ when :content
33
+ @dict[:content] = value
34
+ when :attribute
35
+ @dict[:attributes][key] = value
36
+ when :element
37
+ @dict[:elements][key] = value
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dict_grouping'
4
+ require_relative 'xml'
5
+
6
+ module Shale
7
+ module Mapping
8
+ module Group
9
+ # Class representing mapping group for XML
10
+ #
11
+ # @api private
12
+ class XmlGrouping < DictGrouping
13
+ # Add a value to a group
14
+ #
15
+ # @param [Shale::Mapping::Descriptor::Dict] mapping
16
+ # @param [Symbol] kind
17
+ # @param [any] value
18
+ #
19
+ # @api private
20
+ def add(mapping, kind, value)
21
+ group = @groups[mapping.group] ||= Xml.new(mapping.method_from, mapping.method_to)
22
+ group.add(kind, mapping.namespaced_name, value)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,80 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'descriptor/xml'
4
- require_relative 'descriptor/xml_namespace'
5
- require_relative 'validator'
3
+ require_relative 'xml_base'
4
+ require_relative 'xml_group'
6
5
 
7
6
  module Shale
8
7
  module Mapping
9
- class Xml
10
- # Return elements mapping hash
11
- #
12
- # @return [Hash]
13
- #
14
- # @api private
15
- attr_reader :elements
16
-
17
- # Return attributes mapping hash
18
- #
19
- # @return [Hash]
20
- #
21
- # @api private
22
- attr_reader :attributes
23
-
24
- # Return content mapping
25
- #
26
- # @return [Symbol]
27
- #
28
- # @api private
29
- attr_reader :content
30
-
31
- # Return default namespace
32
- #
33
- # @return [Shale::Mapping::Descriptor::XmlNamespace]
34
- #
35
- # @api private
36
- attr_reader :default_namespace
37
-
38
- # Return unprefixed root
39
- #
40
- # @return [String]
41
- #
42
- # @api private
43
- def unprefixed_root
44
- @root
45
- end
46
-
47
- # Return prefixed root
48
- #
49
- # @return [String]
50
- #
51
- # @api private
52
- def prefixed_root
53
- [default_namespace.prefix, @root].compact.join(':')
54
- end
55
-
56
- # Initialize instance
57
- #
58
- # @api private
59
- def initialize
60
- super
61
- @elements = {}
62
- @attributes = {}
63
- @content = nil
64
- @root = ''
65
- @default_namespace = Descriptor::XmlNamespace.new
66
- @finalized = false
67
- end
68
-
8
+ # Mapping for XML serialization format
9
+ #
10
+ # @api private
11
+ class Xml < XmlBase
69
12
  # Map element to attribute
70
13
  #
71
- # @param [String] element Document's element
72
- # @param [Symbol, nil] to Object's attribute
14
+ # @param [String] element
15
+ # @param [Symbol, nil] to
73
16
  # @param [Hash, nil] using
74
17
  # @param [String, nil] namespace
75
18
  # @param [String, nil] prefix
76
- #
77
- # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
19
+ # @param [true, false] cdata
20
+ # @param [true, false] render_nil
78
21
  #
79
22
  # @api private
80
23
  def map_element(
@@ -86,24 +29,12 @@ module Shale
86
29
  cdata: false,
87
30
  render_nil: false
88
31
  )
89
- Validator.validate_arguments(element, to, using)
90
- Validator.validate_namespace(element, namespace, prefix)
91
-
92
- if namespace == :undefined && prefix == :undefined
93
- nsp = default_namespace.name
94
- pfx = default_namespace.prefix
95
- else
96
- nsp = namespace
97
- pfx = prefix
98
- end
99
-
100
- namespaced_element = [nsp, element].compact.join(':')
101
-
102
- @elements[namespaced_element] = Descriptor::Xml.new(
103
- name: element,
104
- attribute: to,
105
- methods: using,
106
- namespace: Descriptor::XmlNamespace.new(nsp, pfx),
32
+ super(
33
+ element,
34
+ to: to,
35
+ using: using,
36
+ namespace: namespace,
37
+ prefix: prefix,
107
38
  cdata: cdata,
108
39
  render_nil: render_nil
109
40
  )
@@ -111,13 +42,12 @@ module Shale
111
42
 
112
43
  # Map document's attribute to object's attribute
113
44
  #
114
- # @param [String] attribute Document's attribute
115
- # @param [Symbol, nil] to Object's attribute
45
+ # @param [String] attribute
46
+ # @param [Symbol, nil] to
116
47
  # @param [Hash, nil] using
117
48
  # @param [String, nil] namespace
118
49
  # @param [String, nil] prefix
119
- #
120
- # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
50
+ # @param [true, false] render_nil
121
51
  #
122
52
  # @api private
123
53
  def map_attribute(
@@ -128,85 +58,43 @@ module Shale
128
58
  prefix: nil,
129
59
  render_nil: false
130
60
  )
131
- Validator.validate_arguments(attribute, to, using)
132
- Validator.validate_namespace(attribute, namespace, prefix)
133
-
134
- namespaced_attribute = [namespace, attribute].compact.join(':')
135
-
136
- @attributes[namespaced_attribute] = Descriptor::Xml.new(
137
- name: attribute,
138
- attribute: to,
139
- methods: using,
140
- namespace: Descriptor::XmlNamespace.new(namespace, prefix),
141
- cdata: false,
61
+ super(
62
+ attribute,
63
+ to: to,
64
+ using: using,
65
+ namespace: namespace,
66
+ prefix: prefix,
142
67
  render_nil: render_nil
143
68
  )
144
69
  end
145
70
 
146
71
  # Map document's content to object's attribute
147
72
  #
148
- # @param [Symbol] to Object's attribute
73
+ # @param [Symbol] to
74
+ # @param [Hash, nil] using
75
+ # @param [true, false] cdata
149
76
  #
150
77
  # @api private
151
78
  def map_content(to: nil, using: nil, cdata: false)
152
- Validator.validate_arguments('content', to, using)
153
-
154
- @content = Descriptor::Xml.new(
155
- name: nil,
156
- attribute: to,
157
- methods: using,
158
- namespace: nil,
159
- cdata: cdata,
160
- render_nil: false
161
- )
162
- end
163
-
164
- # Set the name for root element
165
- #
166
- # @param [String] value root's name
167
- #
168
- # @api private
169
- def root(value)
170
- @root = value
171
- end
172
-
173
- # Set default namespace for root element
174
- #
175
- # @param [String] name
176
- # @param [String] prefix
177
- #
178
- # @api private
179
- def namespace(name, prefix)
180
- @default_namespace.name = name
181
- @default_namespace.prefix = prefix
79
+ super(to: to, using: using, cdata: cdata)
182
80
  end
183
81
 
184
- # Set the "finalized" instance variable to true
82
+ # Map group of nodes to mapping methods
185
83
  #
186
- # @api private
187
- def finalize!
188
- @finalized = true
189
- end
190
-
191
- # Query the "finalized" instance variable
192
- #
193
- # @return [truem false]
84
+ # @param [Symbol] from
85
+ # @param [Symbol] to
86
+ # @param [Proc] block
194
87
  #
195
88
  # @api private
196
- def finalized?
197
- @finalized
198
- end
89
+ def group(from:, to:, &block)
90
+ group = XmlGroup.new(from, to)
199
91
 
200
- # @api private
201
- def initialize_dup(other)
202
- @elements = other.instance_variable_get('@elements').dup
203
- @attributes = other.instance_variable_get('@attributes').dup
204
- @content = other.instance_variable_get('@content').dup
205
- @root = other.instance_variable_get('@root').dup
206
- @default_namespace = other.instance_variable_get('@default_namespace').dup
207
- @finalized = false
92
+ group.namespace(default_namespace.name, default_namespace.prefix)
93
+ group.instance_eval(&block)
208
94
 
209
- super
95
+ @elements.merge!(group.elements)
96
+ @attributes.merge!(group.attributes)
97
+ @content = group.content if group.content
210
98
  end
211
99
  end
212
100
  end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'descriptor/xml'
4
+ require_relative 'descriptor/xml_namespace'
5
+ require_relative 'validator'
6
+
7
+ module Shale
8
+ module Mapping
9
+ # Base class for Mapping XML serialization format
10
+ #
11
+ # @api private
12
+ class XmlBase
13
+ # Return elements mapping hash
14
+ #
15
+ # @return [Hash]
16
+ #
17
+ # @api private
18
+ attr_reader :elements
19
+
20
+ # Return attributes mapping hash
21
+ #
22
+ # @return [Hash]
23
+ #
24
+ # @api private
25
+ attr_reader :attributes
26
+
27
+ # Return content mapping
28
+ #
29
+ # @return [Symbol]
30
+ #
31
+ # @api private
32
+ attr_reader :content
33
+
34
+ # Return default namespace
35
+ #
36
+ # @return [Shale::Mapping::Descriptor::XmlNamespace]
37
+ #
38
+ # @api private
39
+ attr_reader :default_namespace
40
+
41
+ # Return unprefixed root
42
+ #
43
+ # @return [String]
44
+ #
45
+ # @api private
46
+ def unprefixed_root
47
+ @root
48
+ end
49
+
50
+ # Return prefixed root
51
+ #
52
+ # @return [String]
53
+ #
54
+ # @api private
55
+ def prefixed_root
56
+ [default_namespace.prefix, @root].compact.join(':')
57
+ end
58
+
59
+ # Initialize instance
60
+ #
61
+ # @api private
62
+ def initialize
63
+ super
64
+ @elements = {}
65
+ @attributes = {}
66
+ @content = nil
67
+ @root = ''
68
+ @default_namespace = Descriptor::XmlNamespace.new
69
+ @finalized = false
70
+ end
71
+
72
+ # Map element to attribute
73
+ #
74
+ # @param [String] element
75
+ # @param [Symbol, nil] to
76
+ # @param [Hash, nil] using
77
+ # @param [String, nil] group
78
+ # @param [String, nil] namespace
79
+ # @param [String, nil] prefix
80
+ # @param [true, false] cdata
81
+ # @param [true, false] render_nil
82
+ #
83
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
84
+ #
85
+ # @api private
86
+ def map_element(
87
+ element,
88
+ to: nil,
89
+ using: nil,
90
+ group: nil,
91
+ namespace: :undefined,
92
+ prefix: :undefined,
93
+ cdata: false,
94
+ render_nil: false
95
+ )
96
+ Validator.validate_arguments(element, to, using)
97
+ Validator.validate_namespace(element, namespace, prefix)
98
+
99
+ if namespace == :undefined && prefix == :undefined
100
+ nsp = default_namespace.name
101
+ pfx = default_namespace.prefix
102
+ else
103
+ nsp = namespace
104
+ pfx = prefix
105
+ end
106
+
107
+ namespaced_element = [nsp, element].compact.join(':')
108
+
109
+ @elements[namespaced_element] = Descriptor::Xml.new(
110
+ name: element,
111
+ attribute: to,
112
+ methods: using,
113
+ group: group,
114
+ namespace: Descriptor::XmlNamespace.new(nsp, pfx),
115
+ cdata: cdata,
116
+ render_nil: render_nil
117
+ )
118
+ end
119
+
120
+ # Map document's attribute to object's attribute
121
+ #
122
+ # @param [String] attribute
123
+ # @param [Symbol, nil] to
124
+ # @param [Hash, nil] using
125
+ # @param [String, nil] namespace
126
+ # @param [String, nil] prefix
127
+ # @param [true, false] render_nil
128
+ #
129
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
130
+ #
131
+ # @api private
132
+ def map_attribute(
133
+ attribute,
134
+ to: nil,
135
+ using: nil,
136
+ group: nil,
137
+ namespace: nil,
138
+ prefix: nil,
139
+ render_nil: false
140
+ )
141
+ Validator.validate_arguments(attribute, to, using)
142
+ Validator.validate_namespace(attribute, namespace, prefix)
143
+
144
+ namespaced_attribute = [namespace, attribute].compact.join(':')
145
+
146
+ @attributes[namespaced_attribute] = Descriptor::Xml.new(
147
+ name: attribute,
148
+ attribute: to,
149
+ methods: using,
150
+ namespace: Descriptor::XmlNamespace.new(namespace, prefix),
151
+ cdata: false,
152
+ group: group,
153
+ render_nil: render_nil
154
+ )
155
+ end
156
+
157
+ # Map document's content to object's attribute
158
+ #
159
+ # @param [Symbol] to
160
+ # @param [Hash, nil] using
161
+ # @param [true, false] cdata
162
+ #
163
+ # @api private
164
+ def map_content(to: nil, using: nil, group: nil, cdata: false)
165
+ Validator.validate_arguments('content', to, using)
166
+
167
+ @content = Descriptor::Xml.new(
168
+ name: nil,
169
+ attribute: to,
170
+ methods: using,
171
+ namespace: Descriptor::XmlNamespace.new(nil, nil),
172
+ cdata: cdata,
173
+ group: group,
174
+ render_nil: false
175
+ )
176
+ end
177
+
178
+ # Set the name for root element
179
+ #
180
+ # @param [String] value root's name
181
+ #
182
+ # @api private
183
+ def root(value)
184
+ @root = value
185
+ end
186
+
187
+ # Set default namespace for root element
188
+ #
189
+ # @param [String] name
190
+ # @param [String] prefix
191
+ #
192
+ # @api private
193
+ def namespace(name, prefix)
194
+ @default_namespace.name = name
195
+ @default_namespace.prefix = prefix
196
+ end
197
+
198
+ # Set the "finalized" instance variable to true
199
+ #
200
+ # @api private
201
+ def finalize!
202
+ @finalized = true
203
+ end
204
+
205
+ # Query the "finalized" instance variable
206
+ #
207
+ # @return [truem false]
208
+ #
209
+ # @api private
210
+ def finalized?
211
+ @finalized
212
+ end
213
+
214
+ # @api private
215
+ def initialize_dup(other)
216
+ @elements = other.instance_variable_get('@elements').dup
217
+ @attributes = other.instance_variable_get('@attributes').dup
218
+ @content = other.instance_variable_get('@content').dup
219
+ @root = other.instance_variable_get('@root').dup
220
+ @default_namespace = other.instance_variable_get('@default_namespace').dup
221
+ @finalized = false
222
+
223
+ super
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'xml_base'
4
+
5
+ module Shale
6
+ module Mapping
7
+ # Group for XML serialization format
8
+ #
9
+ # @api private
10
+ class XmlGroup < XmlBase
11
+ # Return name of the group
12
+ #
13
+ # @return [String]
14
+ #
15
+ # @api private
16
+ attr_reader :name
17
+
18
+ # Initialize instance
19
+ #
20
+ # @api private
21
+ def initialize(from, to)
22
+ super()
23
+ @from = from
24
+ @to = to
25
+ @name = "group_#{hash}"
26
+ end
27
+
28
+ # Map element to attribute
29
+ #
30
+ # @param [String] element
31
+ # @param [String, nil] namespace
32
+ # @param [String, nil] prefix
33
+ #
34
+ # @api private
35
+ def map_element(element, namespace: :undefined, prefix: :undefined)
36
+ super(
37
+ element,
38
+ using: { from: @from, to: @to },
39
+ group: @name,
40
+ namespace: namespace,
41
+ prefix: prefix
42
+ )
43
+ end
44
+
45
+ # Map document's attribute to object's attribute
46
+ #
47
+ # @param [String] attribute
48
+ # @param [String, nil] namespace
49
+ # @param [String, nil] prefix
50
+ #
51
+ # @api private
52
+ def map_attribute(attribute, namespace: nil, prefix: nil)
53
+ super(
54
+ attribute,
55
+ using: { from: @from, to: @to },
56
+ group: @name,
57
+ namespace: namespace,
58
+ prefix: prefix
59
+ )
60
+ end
61
+
62
+ # Map document's content to object's attribute
63
+ #
64
+ # @api private
65
+ def map_content
66
+ super(using: { from: @from, to: @to }, group: @name)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../error'
4
+ require_relative '../mapping/group/dict_grouping'
5
+ require_relative '../mapping/group/xml_grouping'
4
6
  require_relative 'value'
5
7
 
6
8
  module Shale
@@ -32,6 +34,7 @@ module Shale
32
34
  .each { |attr| instance.send(attr.setter, attr.default.call) }
33
35
 
34
36
  mapping_keys = #{format}_mapping.keys
37
+ grouping = Shale::Mapping::Group::DictGrouping.new
35
38
 
36
39
  only = to_partial_render_attributes(only)
37
40
  except = to_partial_render_attributes(except)
@@ -40,7 +43,9 @@ module Shale
40
43
  mapping = mapping_keys[key]
41
44
  next unless mapping
42
45
 
43
- if mapping.method_from
46
+ if mapping.group
47
+ grouping.add(mapping, value)
48
+ elsif mapping.method_from
44
49
  mapper = new
45
50
 
46
51
  if mapper.method(mapping.method_from).arity == 3
@@ -89,6 +94,16 @@ module Shale
89
94
  end
90
95
  end
91
96
 
97
+ grouping.each do |group|
98
+ mapper = new
99
+
100
+ if mapper.method(group.method_from).arity == 3
101
+ mapper.send(group.method_from, instance, group.dict, context)
102
+ else
103
+ mapper.send(group.method_from, instance, group.dict)
104
+ end
105
+ end
106
+
92
107
  instance
93
108
  end
94
109
 
@@ -111,12 +126,15 @@ module Shale
111
126
  end
112
127
 
113
128
  hash = {}
129
+ grouping = Shale::Mapping::Group::DictGrouping.new
114
130
 
115
131
  only = to_partial_render_attributes(only)
116
132
  except = to_partial_render_attributes(except)
117
133
 
118
134
  #{format}_mapping.keys.each_value do |mapping|
119
- if mapping.method_to
135
+ if mapping.group
136
+ grouping.add(mapping, nil)
137
+ elsif mapping.method_to
120
138
  mapper = new
121
139
 
122
140
  if mapper.method(mapping.method_to).arity == 3
@@ -166,6 +184,16 @@ module Shale
166
184
  end
167
185
  end
168
186
 
187
+ grouping.each do |group|
188
+ mapper = new
189
+
190
+ if mapper.method(group.method_to).arity == 3
191
+ mapper.send(group.method_to, instance, hash, context)
192
+ else
193
+ mapper.send(group.method_to, instance, hash)
194
+ end
195
+ end
196
+
169
197
  hash
170
198
  end
171
199
  RUBY
@@ -302,6 +330,8 @@ module Shale
302
330
  .select { |attr| attr.default }
303
331
  .each { |attr| instance.send(attr.setter, attr.default.call) }
304
332
 
333
+ grouping = Shale::Mapping::Group::XmlGrouping.new
334
+
305
335
  only = to_partial_render_attributes(only)
306
336
  except = to_partial_render_attributes(except)
307
337
 
@@ -309,7 +339,9 @@ module Shale
309
339
  mapping = xml_mapping.attributes[key.to_s]
310
340
  next unless mapping
311
341
 
312
- if mapping.method_from
342
+ if mapping.group
343
+ grouping.add(mapping, :attribute, value)
344
+ elsif mapping.method_from
313
345
  mapper = new
314
346
 
315
347
  if mapper.method(mapping.method_from).arity == 3
@@ -335,7 +367,9 @@ module Shale
335
367
  content_mapping = xml_mapping.content
336
368
 
337
369
  if content_mapping
338
- if content_mapping.method_from
370
+ if content_mapping.group
371
+ grouping.add(content_mapping, :content, element)
372
+ elsif content_mapping.method_from
339
373
  mapper = new
340
374
 
341
375
  if mapper.method(content_mapping.method_from).arity == 3
@@ -366,7 +400,9 @@ module Shale
366
400
  mapping = xml_mapping.elements[node.name]
367
401
  next unless mapping
368
402
 
369
- if mapping.method_from
403
+ if mapping.group
404
+ grouping.add(mapping, :element, node)
405
+ elsif mapping.method_from
370
406
  mapper = new
371
407
 
372
408
  if mapper.method(mapping.method_from).arity == 3
@@ -403,6 +439,16 @@ module Shale
403
439
  end
404
440
  end
405
441
 
442
+ grouping.each do |group|
443
+ mapper = new
444
+
445
+ if mapper.method(group.method_from).arity == 3
446
+ mapper.send(group.method_from, instance, group.dict, context)
447
+ else
448
+ mapper.send(group.method_from, instance, group.dict)
449
+ end
450
+ end
451
+
406
452
  instance
407
453
  end
408
454
 
@@ -479,11 +525,15 @@ module Shale
479
525
  xml_mapping.default_namespace.name
480
526
  )
481
527
 
528
+ grouping = Shale::Mapping::Group::XmlGrouping.new
529
+
482
530
  only = to_partial_render_attributes(only)
483
531
  except = to_partial_render_attributes(except)
484
532
 
485
533
  xml_mapping.attributes.each_value do |mapping|
486
- if mapping.method_to
534
+ if mapping.group
535
+ grouping.add(mapping, :attribute, nil)
536
+ elsif mapping.method_to
487
537
  mapper = new
488
538
 
489
539
  if mapper.method(mapping.method_to).arity == 4
@@ -510,7 +560,9 @@ module Shale
510
560
  content_mapping = xml_mapping.content
511
561
 
512
562
  if content_mapping
513
- if content_mapping.method_to
563
+ if content_mapping.group
564
+ grouping.add(content_mapping, :content, nil)
565
+ elsif content_mapping.method_to
514
566
  mapper = new
515
567
 
516
568
  if mapper.method(content_mapping.method_to).arity == 4
@@ -543,7 +595,9 @@ module Shale
543
595
  end
544
596
 
545
597
  xml_mapping.elements.each_value do |mapping|
546
- if mapping.method_to
598
+ if mapping.group
599
+ grouping.add(mapping, :element, nil)
600
+ elsif mapping.method_to
547
601
  mapper = new
548
602
 
549
603
  if mapper.method(mapping.method_to).arity == 4
@@ -605,6 +659,16 @@ module Shale
605
659
  end
606
660
  end
607
661
 
662
+ grouping.each do |group|
663
+ mapper = new
664
+
665
+ if mapper.method(group.method_to).arity == 4
666
+ mapper.send(group.method_to, instance, element, doc, context)
667
+ else
668
+ mapper.send(group.method_to, instance, element, doc)
669
+ end
670
+ end
671
+
608
672
  element
609
673
  end
610
674
 
data/lib/shale/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Shale
4
4
  # @api private
5
- VERSION = '0.7.1'
5
+ VERSION = '0.8.0'
6
6
  end
data/shale.gemspec CHANGED
@@ -26,5 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.bindir = 'exe'
27
27
  spec.executables = 'shaleb'
28
28
 
29
- spec.required_ruby_version = '>= 2.7.0'
29
+ spec.required_ruby_version = '>= 2.6.0'
30
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shale
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kamil Giszczak
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-12 00:00:00.000000000 Z
11
+ date: 2022-08-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby object mapper and serializer for XML, JSON, TOML and YAML.
14
14
  email:
@@ -41,8 +41,16 @@ files:
41
41
  - lib/shale/mapping/descriptor/xml.rb
42
42
  - lib/shale/mapping/descriptor/xml_namespace.rb
43
43
  - lib/shale/mapping/dict.rb
44
+ - lib/shale/mapping/dict_base.rb
45
+ - lib/shale/mapping/dict_group.rb
46
+ - lib/shale/mapping/group/dict.rb
47
+ - lib/shale/mapping/group/dict_grouping.rb
48
+ - lib/shale/mapping/group/xml.rb
49
+ - lib/shale/mapping/group/xml_grouping.rb
44
50
  - lib/shale/mapping/validator.rb
45
51
  - lib/shale/mapping/xml.rb
52
+ - lib/shale/mapping/xml_base.rb
53
+ - lib/shale/mapping/xml_group.rb
46
54
  - lib/shale/schema.rb
47
55
  - lib/shale/schema/compiler/boolean.rb
48
56
  - lib/shale/schema/compiler/complex.rb
@@ -108,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
116
  requirements:
109
117
  - - ">="
110
118
  - !ruby/object:Gem::Version
111
- version: 2.7.0
119
+ version: 2.6.0
112
120
  required_rubygems_version: !ruby/object:Gem::Requirement
113
121
  requirements:
114
122
  - - ">="