shale 0.4.0 → 0.7.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +449 -40
  5. data/exe/shaleb +30 -6
  6. data/lib/shale/adapter/json.rb +3 -3
  7. data/lib/shale/adapter/nokogiri/document.rb +97 -0
  8. data/lib/shale/adapter/nokogiri/node.rb +100 -0
  9. data/lib/shale/adapter/nokogiri.rb +17 -156
  10. data/lib/shale/adapter/ox/document.rb +90 -0
  11. data/lib/shale/adapter/ox/node.rb +97 -0
  12. data/lib/shale/adapter/ox.rb +14 -138
  13. data/lib/shale/adapter/rexml/document.rb +98 -0
  14. data/lib/shale/adapter/rexml/node.rb +99 -0
  15. data/lib/shale/adapter/rexml.rb +14 -154
  16. data/lib/shale/adapter/toml_rb.rb +34 -0
  17. data/lib/shale/error.rb +57 -2
  18. data/lib/shale/mapper.rb +61 -9
  19. data/lib/shale/mapping/descriptor/dict.rb +12 -1
  20. data/lib/shale/mapping/descriptor/xml.rb +12 -2
  21. data/lib/shale/mapping/dict.rb +26 -2
  22. data/lib/shale/mapping/xml.rb +52 -6
  23. data/lib/shale/schema/{json_compiler → compiler}/boolean.rb +1 -1
  24. data/lib/shale/schema/{json_compiler/object.rb → compiler/complex.rb} +11 -8
  25. data/lib/shale/schema/{json_compiler → compiler}/date.rb +1 -1
  26. data/lib/shale/schema/{json_compiler → compiler}/float.rb +1 -1
  27. data/lib/shale/schema/{json_compiler → compiler}/integer.rb +1 -1
  28. data/lib/shale/schema/{json_compiler → compiler}/property.rb +6 -6
  29. data/lib/shale/schema/{json_compiler → compiler}/string.rb +1 -1
  30. data/lib/shale/schema/{json_compiler → compiler}/time.rb +1 -1
  31. data/lib/shale/schema/compiler/value.rb +21 -0
  32. data/lib/shale/schema/compiler/xml_complex.rb +50 -0
  33. data/lib/shale/schema/compiler/xml_property.rb +73 -0
  34. data/lib/shale/schema/json_compiler.rb +32 -34
  35. data/lib/shale/schema/json_generator.rb +4 -6
  36. data/lib/shale/schema/xml_compiler.rb +919 -0
  37. data/lib/shale/schema/xml_generator/import.rb +2 -2
  38. data/lib/shale/schema/xml_generator.rb +11 -13
  39. data/lib/shale/schema.rb +16 -0
  40. data/lib/shale/type/complex.rb +763 -0
  41. data/lib/shale/type/value.rb +38 -9
  42. data/lib/shale/utils.rb +42 -7
  43. data/lib/shale/version.rb +1 -1
  44. data/lib/shale.rb +22 -19
  45. data/shale.gemspec +3 -3
  46. metadata +26 -17
  47. data/lib/shale/schema/json_compiler/utils.rb +0 -52
  48. data/lib/shale/schema/json_compiler/value.rb +0 -13
  49. data/lib/shale/type/composite.rb +0 -331
@@ -2,6 +2,10 @@
2
2
 
3
3
  require 'ox'
4
4
 
5
+ require_relative '../error'
6
+ require_relative 'ox/document'
7
+ require_relative 'ox/node'
8
+
5
9
  module Shale
6
10
  module Adapter
7
11
  # Ox adapter
@@ -12,29 +16,34 @@ module Shale
12
16
  #
13
17
  # @param [String] xml XML document
14
18
  #
15
- # @return [::Ox::Document, ::Ox::Element]
19
+ # @raise [ParseError] when XML document has errors
20
+ #
21
+ # @return [Shale::Adapter::Ox::Node]
16
22
  #
17
23
  # @api private
18
24
  def self.load(xml)
19
25
  Node.new(::Ox.parse(xml))
26
+ rescue ::Ox::ParseError => e
27
+ raise ParseError, "Document is invalid: #{e.message}"
20
28
  end
21
29
 
22
30
  # Serialize Ox document into XML
23
31
  #
24
32
  # @param [::Ox::Document, ::Ox::Element] doc Ox document
25
- # @param [Array<Symbol>] options
33
+ # @param [true, false] pretty
34
+ # @param [true, false] declaration
26
35
  #
27
36
  # @return [String]
28
37
  #
29
38
  # @api private
30
- def self.dump(doc, *options)
39
+ def self.dump(doc, pretty: false, declaration: false)
31
40
  opts = { indent: -1, with_xml: false }
32
41
 
33
- if options.include?(:pretty)
42
+ if pretty
34
43
  opts[:indent] = 2
35
44
  end
36
45
 
37
- if options.include?(:declaration)
46
+ if declaration
38
47
  doc[:version] = '1.0'
39
48
  opts[:with_xml] = true
40
49
  end
@@ -48,139 +57,6 @@ module Shale
48
57
  def self.create_document
49
58
  Document.new
50
59
  end
51
-
52
- # Wrapper around Ox API
53
- #
54
- # @api private
55
- class Document
56
- # Return Ox document
57
- #
58
- # @return [::Ox::Document]
59
- #
60
- # @api private
61
- attr_reader :doc
62
-
63
- # Initialize object
64
- #
65
- # @api private
66
- def initialize
67
- @doc = ::Ox::Document.new
68
- end
69
-
70
- # Create Ox element
71
- #
72
- # @param [String] name Name of the XML element
73
- #
74
- # @return [::Ox::Element]
75
- #
76
- # @api private
77
- def create_element(name)
78
- ::Ox::Element.new(name)
79
- end
80
-
81
- # Add XML namespace to document
82
- #
83
- # Ox doesn't support XML namespaces so this method does nothing.
84
- #
85
- # @param [String] prefix
86
- # @param [String] namespace
87
- #
88
- # @api private
89
- def add_namespace(prefix, namespace)
90
- # :noop:
91
- end
92
-
93
- # Add attribute to Ox element
94
- #
95
- # @param [::Ox::Element] element Ox element
96
- # @param [String] name Name of the XML attribute
97
- # @param [String] value Value of the XML attribute
98
- #
99
- # @api private
100
- def add_attribute(element, name, value)
101
- element[name] = value
102
- end
103
-
104
- # Add child element to Ox element
105
- #
106
- # @param [::Ox::Element] element Ox parent element
107
- # @param [::Ox::Element] child Ox child element
108
- #
109
- # @api private
110
- def add_element(element, child)
111
- element << child
112
- end
113
-
114
- # Add text node to Ox element
115
- #
116
- # @param [::Ox::Element] element Ox element
117
- # @param [String] text Text to add
118
- #
119
- # @api private
120
- def add_text(element, text)
121
- element << text
122
- end
123
- end
124
-
125
- # Wrapper around Ox::Element API
126
- #
127
- # @api private
128
- class Node
129
- # Initialize object with Ox element
130
- #
131
- # @param [::Ox::Element] node Ox element
132
- #
133
- # @api private
134
- def initialize(node)
135
- @node = node
136
- end
137
-
138
- # Return name of the node in the format of
139
- # prefix:name when the node is namespaced or just name when it's not
140
- #
141
- # @return [String]
142
- #
143
- # @example without namespace
144
- # node.name # => Bar
145
- #
146
- # @example with namespace
147
- # node.name # => foo:Bar
148
- #
149
- # @api private
150
- def name
151
- @node.name
152
- end
153
-
154
- # Return all attributes associated with the node
155
- #
156
- # @return [Hash]
157
- #
158
- # @api private
159
- def attributes
160
- @node.attributes
161
- end
162
-
163
- # Return node's element children
164
- #
165
- # @return [Array<Shale::Adapter::Ox::Node>]
166
- #
167
- # @api private
168
- def children
169
- @node
170
- .nodes
171
- .filter { |e| e.is_a?(::Ox::Element) }
172
- .map { |e| self.class.new(e) }
173
- end
174
-
175
- # Return first text child of a node
176
- #
177
- # @return [String]
178
- #
179
- # @api private
180
- def text
181
- @node.text
182
- end
183
- end
184
60
  end
185
61
  end
186
62
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Adapter
5
+ module REXML
6
+ # Wrapper around REXML API
7
+ #
8
+ # @api private
9
+ class Document
10
+ # Initialize object
11
+ #
12
+ # @api private
13
+ def initialize
14
+ context = { attribute_quote: :quote, prologue_quote: :quote }
15
+ @doc = ::REXML::Document.new(nil, context)
16
+ @namespaces = {}
17
+ end
18
+
19
+ # Return REXML document
20
+ #
21
+ # @return [::REXML::Document]
22
+ #
23
+ # @api private
24
+ def doc
25
+ if @doc.root
26
+ @namespaces.each do |prefix, namespace|
27
+ @doc.root.add_namespace(prefix, namespace)
28
+ end
29
+ end
30
+
31
+ @doc
32
+ end
33
+
34
+ # Create REXML element
35
+ #
36
+ # @param [String] name Name of the XML element
37
+ #
38
+ # @return [::REXML::Element]
39
+ #
40
+ # @api private
41
+ def create_element(name)
42
+ ::REXML::Element.new(name, nil, attribute_quote: :quote)
43
+ end
44
+
45
+ # Create CDATA node and add it to parent
46
+ #
47
+ # @param [String] text
48
+ # @param [::REXML::Element] parent
49
+ #
50
+ # @api private
51
+ def create_cdata(text, parent)
52
+ ::REXML::CData.new(text, true, parent)
53
+ end
54
+
55
+ # Add XML namespace to document
56
+ #
57
+ # @param [String] prefix
58
+ # @param [String] namespace
59
+ #
60
+ # @api private
61
+ def add_namespace(prefix, namespace)
62
+ @namespaces[prefix] = namespace if prefix && namespace
63
+ end
64
+
65
+ # Add attribute to REXML element
66
+ #
67
+ # @param [::REXML::Element] element REXML element
68
+ # @param [String] name Name of the XML attribute
69
+ # @param [String] value Value of the XML attribute
70
+ #
71
+ # @api private
72
+ def add_attribute(element, name, value)
73
+ element.add_attribute(name, value || '')
74
+ end
75
+
76
+ # Add child element to REXML element
77
+ #
78
+ # @param [::REXML::Element] element REXML parent element
79
+ # @param [::REXML::Element] child REXML child element
80
+ #
81
+ # @api private
82
+ def add_element(element, child)
83
+ element.add_element(child)
84
+ end
85
+
86
+ # Add text node to REXML element
87
+ #
88
+ # @param [::REXML::Element] element REXML element
89
+ # @param [String] text Text to add
90
+ #
91
+ # @api private
92
+ def add_text(element, text)
93
+ element.add_text(text)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -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,30 +16,35 @@ 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
25
32
  #
26
33
  # @param [::REXML::Document] doc REXML document
27
- # @param [Array<Symbol>] options
34
+ # @param [true, false] pretty
35
+ # @param [true, false] declaration
28
36
  #
29
37
  # @return [String]
30
38
  #
31
39
  # @api private
32
- def self.dump(doc, *options)
33
- if options.include?(:declaration)
40
+ def self.dump(doc, pretty: false, declaration: false)
41
+ if declaration
34
42
  doc.add(::REXML::XMLDecl.new)
35
43
  end
36
44
 
37
45
  io = StringIO.new
38
46
 
39
- if options.include?(:pretty)
47
+ if pretty
40
48
  formatter = ::REXML::Formatters::Pretty.new
41
49
  formatter.compact = true
42
50
  else
@@ -53,154 +61,6 @@ module Shale
53
61
  def self.create_document
54
62
  Document.new
55
63
  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
64
  end
205
65
  end
206
66
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'toml-rb'
4
+
5
+ module Shale
6
+ module Adapter
7
+ # TOML adapter
8
+ #
9
+ # @api public
10
+ class TomlRB
11
+ # Parse TOML into Hash
12
+ #
13
+ # @param [String] toml TOML document
14
+ #
15
+ # @return [Hash]
16
+ #
17
+ # @api private
18
+ def self.load(toml)
19
+ ::TomlRB.parse(toml)
20
+ end
21
+
22
+ # Serialize Hash into TOML
23
+ #
24
+ # @param [Hash] obj Hash object
25
+ #
26
+ # @return [String]
27
+ #
28
+ # @api private
29
+ def self.dump(obj)
30
+ ::TomlRB.dump(obj)
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/shale/error.rb CHANGED
@@ -1,6 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shale
4
+ # Error message displayed when TOML adapter is not set
5
+ # @api private
6
+ TOML_ADAPTER_NOT_SET_MESSAGE = <<~MSG
7
+ TOML Adapter is not set.
8
+ To use Shale with TOML documents you have to install parser and set adapter.
9
+
10
+ # To use Tomlib:
11
+ # Make sure tomlib is installed eg. execute: gem install tomlib
12
+ Shale.toml_adapter = Tomlib
13
+
14
+ # To use toml-rb:
15
+ # Make sure toml-rb is installed eg. execute: gem install toml-rb
16
+ require 'shale/adapter/toml_rb'
17
+ Shale.toml_adapter = Shale::Adapter::TomlRB
18
+ MSG
19
+
20
+ # Error message displayed when XML adapter is not set
21
+ # @api private
22
+ XML_ADAPTER_NOT_SET_MESSAGE = <<~MSG
23
+ XML Adapter is not set.
24
+ To use Shale with XML documents you have to install parser and set adapter.
25
+
26
+ # To use REXML:
27
+ require 'shale/adapter/rexml'
28
+ Shale.xml_adapter = Shale::Adapter::REXML
29
+
30
+ # To use Nokogiri:
31
+ # Make sure Nokogiri is installed eg. execute: gem install nokogiri
32
+ require 'shale/adapter/nokogiri'
33
+ Shale.xml_adapter = Shale::Adapter::Nokogiri
34
+
35
+ # To use OX:
36
+ # Make sure Ox is installed eg. execute: gem install ox
37
+ require 'shale/adapter/ox'
38
+ Shale.xml_adapter = Shale::Adapter::Ox
39
+ MSG
40
+
4
41
  # Error for assigning value to not existing attribute
5
42
  #
6
43
  # @api private
@@ -31,6 +68,12 @@ module Shale
31
68
  end
32
69
  end
33
70
 
71
+ # Error for passing incorrect model type
72
+ #
73
+ # @api private
74
+ class IncorrectModelError < StandardError
75
+ end
76
+
34
77
  # Error for passing incorrect arguments to map functions
35
78
  #
36
79
  # @api private
@@ -43,9 +86,21 @@ module Shale
43
86
  class NotAShaleMapperError < StandardError
44
87
  end
45
88
 
46
- # JSON Schema compilation error
89
+ # Schema compilation error
90
+ #
91
+ # @api private
92
+ class SchemaError < StandardError
93
+ end
94
+
95
+ # Parsing error
96
+ #
97
+ # @api private
98
+ class ParseError < StandardError
99
+ end
100
+
101
+ # Adapter error
47
102
  #
48
103
  # @api private
49
- class JSONSchemaError < StandardError
104
+ class AdapterError < StandardError
50
105
  end
51
106
  end