shale 0.3.0 → 0.5.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +200 -21
  4. data/exe/shaleb +108 -36
  5. data/lib/shale/adapter/nokogiri/document.rb +87 -0
  6. data/lib/shale/adapter/nokogiri/node.rb +100 -0
  7. data/lib/shale/adapter/nokogiri.rb +11 -151
  8. data/lib/shale/adapter/ox/document.rb +80 -0
  9. data/lib/shale/adapter/ox/node.rb +88 -0
  10. data/lib/shale/adapter/ox.rb +9 -134
  11. data/lib/shale/adapter/rexml/document.rb +88 -0
  12. data/lib/shale/adapter/rexml/node.rb +99 -0
  13. data/lib/shale/adapter/rexml.rb +9 -150
  14. data/lib/shale/attribute.rb +6 -0
  15. data/lib/shale/error.rb +39 -0
  16. data/lib/shale/mapper.rb +8 -6
  17. data/lib/shale/schema/compiler/boolean.rb +21 -0
  18. data/lib/shale/schema/compiler/complex.rb +88 -0
  19. data/lib/shale/schema/compiler/date.rb +21 -0
  20. data/lib/shale/schema/compiler/float.rb +21 -0
  21. data/lib/shale/schema/compiler/integer.rb +21 -0
  22. data/lib/shale/schema/compiler/property.rb +70 -0
  23. data/lib/shale/schema/compiler/string.rb +21 -0
  24. data/lib/shale/schema/compiler/time.rb +21 -0
  25. data/lib/shale/schema/compiler/value.rb +21 -0
  26. data/lib/shale/schema/compiler/xml_complex.rb +50 -0
  27. data/lib/shale/schema/compiler/xml_property.rb +73 -0
  28. data/lib/shale/schema/json_compiler.rb +331 -0
  29. data/lib/shale/schema/{json → json_generator}/base.rb +2 -2
  30. data/lib/shale/schema/{json → json_generator}/boolean.rb +1 -1
  31. data/lib/shale/schema/{json → json_generator}/collection.rb +2 -2
  32. data/lib/shale/schema/{json → json_generator}/date.rb +1 -1
  33. data/lib/shale/schema/{json → json_generator}/float.rb +1 -1
  34. data/lib/shale/schema/{json → json_generator}/integer.rb +1 -1
  35. data/lib/shale/schema/{json → json_generator}/object.rb +5 -2
  36. data/lib/shale/schema/{json → json_generator}/ref.rb +1 -1
  37. data/lib/shale/schema/{json → json_generator}/schema.rb +7 -5
  38. data/lib/shale/schema/{json → json_generator}/string.rb +1 -1
  39. data/lib/shale/schema/{json → json_generator}/time.rb +1 -1
  40. data/lib/shale/schema/json_generator/value.rb +23 -0
  41. data/lib/shale/schema/{json.rb → json_generator.rb} +36 -36
  42. data/lib/shale/schema/xml_compiler.rb +919 -0
  43. data/lib/shale/schema/{xml → xml_generator}/attribute.rb +1 -1
  44. data/lib/shale/schema/{xml → xml_generator}/complex_type.rb +5 -2
  45. data/lib/shale/schema/{xml → xml_generator}/element.rb +1 -1
  46. data/lib/shale/schema/{xml → xml_generator}/import.rb +1 -1
  47. data/lib/shale/schema/{xml → xml_generator}/ref_attribute.rb +1 -1
  48. data/lib/shale/schema/{xml → xml_generator}/ref_element.rb +1 -1
  49. data/lib/shale/schema/{xml → xml_generator}/schema.rb +5 -5
  50. data/lib/shale/schema/{xml → xml_generator}/typed_attribute.rb +1 -1
  51. data/lib/shale/schema/{xml → xml_generator}/typed_element.rb +1 -1
  52. data/lib/shale/schema/{xml.rb → xml_generator.rb} +25 -26
  53. data/lib/shale/schema.rb +44 -5
  54. data/lib/shale/type/{composite.rb → complex.rb} +34 -22
  55. data/lib/shale/utils.rb +42 -7
  56. data/lib/shale/version.rb +1 -1
  57. data/lib/shale.rb +8 -19
  58. data/shale.gemspec +1 -1
  59. metadata +47 -27
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../utils'
4
+
5
+ module Shale
6
+ module Adapter
7
+ module REXML
8
+ # Wrapper around REXML::Element API
9
+ #
10
+ # @api private
11
+ class Node
12
+ # Initialize object with REXML element
13
+ #
14
+ # @param [::REXML::Element] node REXML element
15
+ #
16
+ # @api private
17
+ def initialize(node)
18
+ @node = node
19
+ end
20
+
21
+ # Return namespaces defined on document
22
+ #
23
+ # @return [Hash<String, String>]
24
+ #
25
+ # @example
26
+ # node.namespaces # => { 'foo' => 'http://foo.com', 'bar' => 'http://bar.com' }
27
+ #
28
+ # @api private
29
+ def namespaces
30
+ @node.namespaces
31
+ end
32
+
33
+ # Return name of the node in the format of
34
+ # namespace:name when the node is namespaced or just name when it's not
35
+ #
36
+ # @return [String]
37
+ #
38
+ # @example without namespace
39
+ # node.name # => Bar
40
+ #
41
+ # @example with namespace
42
+ # node.name # => http://foo:Bar
43
+ #
44
+ # @api private
45
+ def name
46
+ [Utils.presence(@node.namespace), @node.name].compact.join(':')
47
+ end
48
+
49
+ # Return all attributes associated with the node
50
+ #
51
+ # @return [Hash]
52
+ #
53
+ # @api private
54
+ def attributes
55
+ attributes = @node.attributes.values.map do |attribute|
56
+ attribute.is_a?(Hash) ? attribute.values : attribute
57
+ end.flatten
58
+
59
+ attributes.each_with_object({}) do |attribute, hash|
60
+ name = [Utils.presence(attribute.namespace), attribute.name].compact.join(':')
61
+ hash[name] = attribute.value
62
+ end
63
+ end
64
+
65
+ # Return node's parent
66
+ #
67
+ # @return [Shale::Adapter::REXML::Node, nil]
68
+ #
69
+ # @api private
70
+ def parent
71
+ if @node.parent && @node.parent.name != ''
72
+ self.class.new(@node.parent)
73
+ end
74
+ end
75
+
76
+ # Return node's element children
77
+ #
78
+ # @return [Array<Shale::Adapter::REXML::Node>]
79
+ #
80
+ # @api private
81
+ def children
82
+ @node
83
+ .children
84
+ .filter { |e| e.node_type == :element }
85
+ .map { |e| self.class.new(e) }
86
+ end
87
+
88
+ # Return first text child of a node
89
+ #
90
+ # @return [String]
91
+ #
92
+ # @api private
93
+ def text
94
+ @node.text
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rexml/document'
4
- require_relative '../utils'
4
+
5
+ require_relative '../error'
6
+ require_relative 'rexml/document'
7
+ require_relative 'rexml/node'
5
8
 
6
9
  module Shale
7
10
  module Adapter
@@ -13,12 +16,16 @@ module Shale
13
16
  #
14
17
  # @param [String] xml XML document
15
18
  #
16
- # @return [::REXML::Document]
19
+ # @raise [ParseError] when XML document has errors
20
+ #
21
+ # @return [Shale::Adapter::REXML::Node]
17
22
  #
18
23
  # @api private
19
24
  def self.load(xml)
20
25
  doc = ::REXML::Document.new(xml, ignore_whitespace_nodes: :all)
21
26
  Node.new(doc.root)
27
+ rescue ::REXML::ParseException => e
28
+ raise ParseError, "Document is invalid: #{e.message}"
22
29
  end
23
30
 
24
31
  # Serialize REXML document into XML
@@ -53,154 +60,6 @@ module Shale
53
60
  def self.create_document
54
61
  Document.new
55
62
  end
56
-
57
- # Wrapper around REXML API
58
- #
59
- # @api private
60
- class Document
61
- # Initialize object
62
- #
63
- # @api private
64
- def initialize
65
- context = { attribute_quote: :quote, prologue_quote: :quote }
66
- @doc = ::REXML::Document.new(nil, context)
67
- @namespaces = {}
68
- end
69
-
70
- # Return REXML document
71
- #
72
- # @return [::REXML::Document]
73
- #
74
- # @api private
75
- def doc
76
- if @doc.root
77
- @namespaces.each do |prefix, namespace|
78
- @doc.root.add_namespace(prefix, namespace)
79
- end
80
- end
81
-
82
- @doc
83
- end
84
-
85
- # Create REXML element
86
- #
87
- # @param [String] name Name of the XML element
88
- #
89
- # @return [::REXML::Element]
90
- #
91
- # @api private
92
- def create_element(name)
93
- ::REXML::Element.new(name, nil, attribute_quote: :quote)
94
- end
95
-
96
- # Add XML namespace to document
97
- #
98
- # @param [String] prefix
99
- # @param [String] namespace
100
- #
101
- # @api private
102
- def add_namespace(prefix, namespace)
103
- @namespaces[prefix] = namespace if prefix && namespace
104
- end
105
-
106
- # Add attribute to REXML element
107
- #
108
- # @param [::REXML::Element] element REXML element
109
- # @param [String] name Name of the XML attribute
110
- # @param [String] value Value of the XML attribute
111
- #
112
- # @api private
113
- def add_attribute(element, name, value)
114
- element.add_attribute(name, value)
115
- end
116
-
117
- # Add child element to REXML element
118
- #
119
- # @param [::REXML::Element] element REXML parent element
120
- # @param [::REXML::Element] child REXML child element
121
- #
122
- # @api private
123
- def add_element(element, child)
124
- element.add_element(child)
125
- end
126
-
127
- # Add text node to REXML element
128
- #
129
- # @param [::REXML::Element] element REXML element
130
- # @param [String] text Text to add
131
- #
132
- # @api private
133
- def add_text(element, text)
134
- element.add_text(text)
135
- end
136
- end
137
-
138
- # Wrapper around REXML::Element API
139
- #
140
- # @api private
141
- class Node
142
- # Initialize object with REXML element
143
- #
144
- # @param [::REXML::Element] node REXML element
145
- #
146
- # @api private
147
- def initialize(node)
148
- @node = node
149
- end
150
-
151
- # Return name of the node in the format of
152
- # namespace:name when the node is namespaced or just name when it's not
153
- #
154
- # @return [String]
155
- #
156
- # @example without namespace
157
- # node.name # => Bar
158
- #
159
- # @example with namespace
160
- # node.name # => http://foo:Bar
161
- #
162
- # @api private
163
- def name
164
- [Utils.presence(@node.namespace), @node.name].compact.join(':')
165
- end
166
-
167
- # Return all attributes associated with the node
168
- #
169
- # @return [Hash]
170
- #
171
- # @api private
172
- def attributes
173
- attributes = @node.attributes.values.map do |attribute|
174
- attribute.is_a?(Hash) ? attribute.values : attribute
175
- end.flatten
176
-
177
- attributes.each_with_object({}) do |attribute, hash|
178
- name = [Utils.presence(attribute.namespace), attribute.name].compact.join(':')
179
- hash[name] = attribute.value
180
- end
181
- end
182
-
183
- # Return node's element children
184
- #
185
- # @return [Array<Shale::Adapter::REXML::Node>]
186
- #
187
- # @api private
188
- def children
189
- @node
190
- .children
191
- .filter { |e| e.node_type == :element }
192
- .map { |e| self.class.new(e) }
193
- end
194
-
195
- # Return first text child of a node
196
- #
197
- # @return [String]
198
- #
199
- # @api private
200
- def text
201
- @node.text
202
- end
203
- end
204
63
  end
205
64
  end
206
65
  end
@@ -20,6 +20,11 @@ module Shale
20
20
  # @api private
21
21
  attr_reader :default
22
22
 
23
+ # Return setter name
24
+ #
25
+ # @api private
26
+ attr_reader :setter
27
+
23
28
  # Initialize Attribute object
24
29
  #
25
30
  # @param [Symbol] name Name of the attribute
@@ -30,6 +35,7 @@ module Shale
30
35
  # @api private
31
36
  def initialize(name, type, collection, default)
32
37
  @name = name
38
+ @setter = "#{name}="
33
39
  @type = type
34
40
  @collection = collection
35
41
  @default = collection ? -> { [] } : default
data/lib/shale/error.rb CHANGED
@@ -1,6 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shale
4
+ # Error message displayed when adapter is not set
5
+ # @api private
6
+ ADAPTER_NOT_SET_MESSAGE = <<~MSG
7
+ XML Adapter is not set.
8
+ To use Shale with XML documents you have to install parser and set adapter.
9
+
10
+ To use REXML:
11
+ require 'shale/adapter/rexml'
12
+ Shale.xml_adapter = Shale::Adapter::REXML
13
+
14
+ To use Nokogiri:
15
+ # Make sure Nokogiri is installed eg. execute: gem install nokogiri
16
+ require 'shale/adapter/nokogiri'
17
+ Shale.xml_adapter = Shale::Adapter::Nokogiri
18
+
19
+ To use OX:
20
+ # Make sure Ox is installed eg. execute: gem install ox
21
+ require 'shale/adapter/ox'
22
+ Shale.xml_adapter = Shale::Adapter::Ox
23
+ MSG
24
+
4
25
  # Error for assigning value to not existing attribute
5
26
  #
6
27
  # @api private
@@ -42,4 +63,22 @@ module Shale
42
63
  # @api private
43
64
  class NotAShaleMapperError < StandardError
44
65
  end
66
+
67
+ # Schema compilation error
68
+ #
69
+ # @api private
70
+ class SchemaError < StandardError
71
+ end
72
+
73
+ # Parsing error
74
+ #
75
+ # @api private
76
+ class ParseError < StandardError
77
+ end
78
+
79
+ # Adapter error
80
+ #
81
+ # @api private
82
+ class AdapterError < StandardError
83
+ end
45
84
  end
data/lib/shale/mapper.rb CHANGED
@@ -5,7 +5,7 @@ require_relative 'error'
5
5
  require_relative 'utils'
6
6
  require_relative 'mapping/dict'
7
7
  require_relative 'mapping/xml'
8
- require_relative 'type/composite'
8
+ require_relative 'type/complex'
9
9
 
10
10
  module Shale
11
11
  # Base class used for mapping
@@ -42,7 +42,7 @@ module Shale
42
42
  # person.to_json
43
43
  #
44
44
  # @api public
45
- class Mapper < Type::Composite
45
+ class Mapper < Type::Complex
46
46
  @attributes = {}
47
47
  @hash_mapping = Mapping::Dict.new
48
48
  @json_mapping = Mapping::Dict.new
@@ -272,9 +272,11 @@ module Shale
272
272
  def initialize(**props)
273
273
  super()
274
274
 
275
- props.each_key do |name|
276
- unless self.class.attributes.keys.include?(name)
277
- raise UnknownAttributeError.new(self.class.to_s, name.to_s)
275
+ unless props.empty?
276
+ unknown_attributes = props.keys - self.class.attributes.keys
277
+
278
+ unless unknown_attributes.empty?
279
+ raise UnknownAttributeError.new(self.class.to_s, unknown_attributes[0].to_s)
278
280
  end
279
281
  end
280
282
 
@@ -285,7 +287,7 @@ module Shale
285
287
  value = attribute.default.call
286
288
  end
287
289
 
288
- public_send("#{name}=", value)
290
+ send(attribute.setter, value)
289
291
  end
290
292
  end
291
293
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Schema
5
+ module Compiler
6
+ # Class that maps Schema type to Shale Boolean type
7
+ #
8
+ # @api private
9
+ class Boolean
10
+ # Return name of the Shale type
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ def name
16
+ 'Shale::Type::Boolean'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../utils'
4
+
5
+ module Shale
6
+ module Schema
7
+ module Compiler
8
+ # Class representing Shale's complex type
9
+ #
10
+ # @api private
11
+ class Complex
12
+ # Return id
13
+ #
14
+ # @return [String]
15
+ #
16
+ # @api private
17
+ attr_reader :id
18
+
19
+ # Return properties
20
+ #
21
+ # @return [Array<Shale::Schema::Compiler::Property>]
22
+ #
23
+ # @api private
24
+ attr_reader :properties
25
+
26
+ # Name setter
27
+ #
28
+ # @param [String] val
29
+ #
30
+ # @api private
31
+ attr_writer :name
32
+
33
+ # Initialize object
34
+ #
35
+ # @param [String] id
36
+ # @param [String] name
37
+ #
38
+ # @api private
39
+ def initialize(id, name)
40
+ @id = id
41
+ @name = name
42
+ @properties = []
43
+ end
44
+
45
+ # Return name
46
+ #
47
+ # @return [String]
48
+ #
49
+ # @api private
50
+ def name
51
+ Utils.classify(@name)
52
+ end
53
+
54
+ # Return file name
55
+ #
56
+ # @return [String]
57
+ #
58
+ # @api private
59
+ def file_name
60
+ Utils.snake_case(@name)
61
+ end
62
+
63
+ # Return references
64
+ #
65
+ # @return [Array<Shale::Schema::Compiler::Property>]
66
+ #
67
+ # @api private
68
+ def references
69
+ @properties
70
+ .filter { |e| e.type.is_a?(self.class) && e.type != self }
71
+ .uniq { |e| e.type.id }
72
+ .sort { |a, b| a.type.file_name <=> b.type.file_name }
73
+ end
74
+
75
+ # Add property to Object
76
+ #
77
+ # @param [Shale::Schema::Compiler::Property] property
78
+ #
79
+ # @api private
80
+ def add_property(property)
81
+ unless @properties.find { |e| e.mapping_name == property.mapping_name }
82
+ @properties << property
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Schema
5
+ module Compiler
6
+ # Class that maps Schema type to Shale Date type
7
+ #
8
+ # @api private
9
+ class Date
10
+ # Return name of the Shale type
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ def name
16
+ 'Shale::Type::Date'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Schema
5
+ module Compiler
6
+ # Class that maps Schema type to Shale Float type
7
+ #
8
+ # @api private
9
+ class Float
10
+ # Return name of the Shale type
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ def name
16
+ 'Shale::Type::Float'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Schema
5
+ module Compiler
6
+ # Class that maps Schema type to Shale Integer type
7
+ #
8
+ # @api private
9
+ class Integer
10
+ # Return name of the Shale type
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ def name
16
+ 'Shale::Type::Integer'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../utils'
4
+
5
+ module Shale
6
+ module Schema
7
+ module Compiler
8
+ # Class representing Shale's property
9
+ #
10
+ # @api private
11
+ class Property
12
+ # Return mapping's name
13
+ #
14
+ # @return [String]
15
+ #
16
+ # @api private
17
+ attr_reader :mapping_name
18
+
19
+ # Return attribute's name
20
+ #
21
+ # @return [String]
22
+ #
23
+ # @api private
24
+ attr_reader :attribute_name
25
+
26
+ # Return types's name
27
+ #
28
+ # @return [String]
29
+ #
30
+ # @api private
31
+ attr_reader :type
32
+
33
+ # Initialize Property object
34
+ #
35
+ # @param [String] name
36
+ # @param [Shale::Schema::Compiler::Type] type
37
+ # @param [true, false] collection
38
+ # @param [Object] default
39
+ #
40
+ # @api private
41
+ def initialize(name, type, collection, default)
42
+ @mapping_name = name
43
+ @attribute_name = Utils.snake_case(name)
44
+ @type = type
45
+ @collection = collection
46
+ @default = default
47
+ end
48
+
49
+ # Return whether property is a collection
50
+ #
51
+ # @return [true, false]
52
+ #
53
+ # @api private
54
+ def collection?
55
+ @collection
56
+ end
57
+
58
+ # Return default value
59
+ #
60
+ # @return [nil, Object]
61
+ #
62
+ # @api private
63
+ def default
64
+ return if collection?
65
+ @default.is_a?(::String) ? @default.dump : @default
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Schema
5
+ module Compiler
6
+ # Class that maps Schema type to Shale String type
7
+ #
8
+ # @api private
9
+ class String
10
+ # Return name of the Shale type
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ def name
16
+ 'Shale::Type::String'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Schema
5
+ module Compiler
6
+ # Class that maps Schema type to Shale Time type
7
+ #
8
+ # @api private
9
+ class Time
10
+ # Return name of the Shale type
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ def name
16
+ 'Shale::Type::Time'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Schema
5
+ module Compiler
6
+ # Class that maps Schema type to Shale Value type
7
+ #
8
+ # @api private
9
+ class Value
10
+ # Return name of the Shale type
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ def name
16
+ 'Shale::Type::Value'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end