shale 0.7.1 → 0.8.0

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