shale 0.3.0 → 0.5.0

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