shale 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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
data/exe/shaleb CHANGED
@@ -4,12 +4,29 @@
4
4
  require 'fileutils'
5
5
  require 'optparse'
6
6
 
7
- base_path = File.expand_path('../lib', __dir__)
7
+ def require_local_or_global(path)
8
+ base_path = File.expand_path('../lib', __dir__)
8
9
 
9
- if File.exist?(base_path)
10
- require_relative '../lib/shale/schema'
11
- else
12
- require 'shale/schema'
10
+ if File.exist?(base_path)
11
+ require_relative "../lib/#{path}"
12
+ else
13
+ require path
14
+ end
15
+ end
16
+
17
+ require_local_or_global('shale/schema')
18
+
19
+ def load_xml_parser
20
+ require_local_or_global('shale/adapter/nokogiri')
21
+ Shale.xml_adapter = Shale::Adapter::Nokogiri
22
+ rescue LoadError
23
+ begin
24
+ require_local_or_global('shale/adapter/rexml')
25
+ Shale.xml_adapter = Shale::Adapter::REXML
26
+ rescue LoadError
27
+ puts "Can't load XML parser. Make sure Nokogiri or REXML is installed on your system!"
28
+ exit
29
+ end
13
30
  end
14
31
 
15
32
  params = {}
@@ -54,7 +71,12 @@ if params[:compile]
54
71
  end
55
72
  end
56
73
 
57
- models = Shale::Schema.from_json(schemas, root_name: params[:root])
74
+ if params[:format] == 'xml'
75
+ load_xml_parser
76
+ models = Shale::Schema.from_xml(schemas)
77
+ else
78
+ models = Shale::Schema.from_json(schemas, root_name: params[:root])
79
+ end
58
80
 
59
81
  if params[:output]
60
82
  dir = File.expand_path(params[:output], Dir.pwd)
@@ -94,6 +116,8 @@ else
94
116
  klass = Object.const_get(params[:root])
95
117
 
96
118
  if params[:format] == 'xml'
119
+ load_xml_parser
120
+
97
121
  if params[:output]
98
122
  base_name = File.basename(params[:output], File.extname(params[:output]))
99
123
  schemas = Shale::Schema.to_xml(klass, base_name, pretty: params[:pretty])
@@ -22,13 +22,13 @@ module Shale
22
22
  # Serialize Hash into JSON
23
23
  #
24
24
  # @param [Hash] obj Hash object
25
- # @param [Array<Symbol>] options
25
+ # @param [true, false] pretty
26
26
  #
27
27
  # @return [String]
28
28
  #
29
29
  # @api private
30
- def self.dump(obj, *options)
31
- if options.include?(:pretty)
30
+ def self.dump(obj, pretty: false)
31
+ if pretty
32
32
  ::JSON.pretty_generate(obj)
33
33
  else
34
34
  ::JSON.generate(obj)
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Adapter
5
+ module Nokogiri
6
+ # Wrapper around Nokogiri API
7
+ #
8
+ # @api private
9
+ class Document
10
+ # Initialize object
11
+ #
12
+ # @api private
13
+ def initialize
14
+ @doc = ::Nokogiri::XML::Document.new
15
+ @namespaces = {}
16
+ end
17
+
18
+ # Return Nokogiri document
19
+ #
20
+ # @return [::Nokogiri::XML::Document]
21
+ #
22
+ # @api private
23
+ def doc
24
+ if @doc.root
25
+ @namespaces.each do |prefix, namespace|
26
+ @doc.root.add_namespace(prefix, namespace)
27
+ end
28
+ end
29
+
30
+ @doc
31
+ end
32
+
33
+ # Create Nokogiri element
34
+ #
35
+ # @param [String] name Name of the XML element
36
+ #
37
+ # @return [::Nokogiri::XML::Element]
38
+ #
39
+ # @api private
40
+ def create_element(name)
41
+ ::Nokogiri::XML::Element.new(name, @doc)
42
+ end
43
+
44
+ # Create CDATA node and add it to parent
45
+ #
46
+ # @param [String] text
47
+ # @param [::Nokogiri::XML::Element] parent
48
+ #
49
+ # @api private
50
+ def create_cdata(text, parent)
51
+ parent.add_child(::Nokogiri::XML::CDATA.new(@doc, text))
52
+ end
53
+
54
+ # Add XML namespace to document
55
+ #
56
+ # @param [String] prefix
57
+ # @param [String] namespace
58
+ #
59
+ # @api private
60
+ def add_namespace(prefix, namespace)
61
+ @namespaces[prefix] = namespace if prefix && namespace
62
+ end
63
+
64
+ # Add attribute to Nokogiri element
65
+ #
66
+ # @param [::Nokogiri::XML::Element] element Nokogiri element
67
+ # @param [String] name Name of the XML attribute
68
+ # @param [String] value Value of the XML attribute
69
+ #
70
+ # @api private
71
+ def add_attribute(element, name, value)
72
+ element[name] = value
73
+ end
74
+
75
+ # Add child element to Nokogiri element
76
+ #
77
+ # @param [::Nokogiri::XML::Element] element Nokogiri parent element
78
+ # @param [::Nokogiri::XML::Element] child Nokogiri child element
79
+ #
80
+ # @api private
81
+ def add_element(element, child)
82
+ element.add_child(child)
83
+ end
84
+
85
+ # Add text node to Nokogiri element
86
+ #
87
+ # @param [::Nokogiri::XML::Element] element Nokogiri element
88
+ # @param [String] text Text to add
89
+ #
90
+ # @api private
91
+ def add_text(element, text)
92
+ element.content = text
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Adapter
5
+ module Nokogiri
6
+ # Wrapper around Nokogiri::XML::Node API
7
+ #
8
+ # @api private
9
+ class Node
10
+ # Initialize object with Nokogiri node
11
+ #
12
+ # @param [::Nokogiri::XML::Node] node Nokogiri node
13
+ #
14
+ # @api private
15
+ def initialize(node)
16
+ @node = node
17
+ end
18
+
19
+ # Return namespaces defined on document
20
+ #
21
+ # @return [Hash<String, String>]
22
+ #
23
+ # @example
24
+ # node.namespaces # => { 'foo' => 'http://foo.com', 'bar' => 'http://bar.com' }
25
+ #
26
+ # @api private
27
+ def namespaces
28
+ @node.namespaces.transform_keys { |e| e.sub('xmlns:', '') }
29
+ end
30
+
31
+ # Return name of the node in the format of
32
+ # namespace:name when the node is namespaced or just name when it's not
33
+ #
34
+ # @return [String]
35
+ #
36
+ # @example without namespace
37
+ # node.name # => Bar
38
+ #
39
+ # @example with namespace
40
+ # node.name # => http://foo:Bar
41
+ #
42
+ # @api private
43
+ def name
44
+ [@node.namespace&.href, @node.name].compact.join(':')
45
+ end
46
+
47
+ # Return all attributes associated with the node
48
+ #
49
+ # @return [Hash]
50
+ #
51
+ # @api private
52
+ def attributes
53
+ @node.attribute_nodes.each_with_object({}) do |node, hash|
54
+ name = [node.namespace&.href, node.name].compact.join(':')
55
+ hash[name] = node.value
56
+ end
57
+ end
58
+
59
+ # Return node's parent
60
+ #
61
+ # @return [Shale::Adapter::Nokogiri::Node, nil]
62
+ #
63
+ # @api private
64
+ def parent
65
+ if @node.respond_to?(:parent) && @node.parent && @node.parent.name != 'document'
66
+ self.class.new(@node.parent)
67
+ end
68
+ end
69
+
70
+ # Return node's element children
71
+ #
72
+ # @return [Array<Shale::Adapter::Nokogiri::Node>]
73
+ #
74
+ # @api private
75
+ def children
76
+ @node
77
+ .children
78
+ .to_a
79
+ .filter(&:element?)
80
+ .map { |e| self.class.new(e) }
81
+ end
82
+
83
+ # Return first text child of a node
84
+ #
85
+ # @return [String]
86
+ #
87
+ # @api private
88
+ def text
89
+ first = @node
90
+ .children
91
+ .to_a
92
+ .filter { |e| e.text? || e.cdata? }
93
+ .first
94
+
95
+ first&.text
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -2,6 +2,10 @@
2
2
 
3
3
  require 'nokogiri'
4
4
 
5
+ require_relative '../error'
6
+ require_relative 'nokogiri/document'
7
+ require_relative 'nokogiri/node'
8
+
5
9
  module Shale
6
10
  module Adapter
7
11
  # Nokogiri adapter
@@ -12,7 +16,9 @@ module Shale
12
16
  #
13
17
  # @param [String] xml XML document
14
18
  #
15
- # @return [::Nokogiri::XML::Document]
19
+ # @raise [ParseError] when XML document has errors
20
+ #
21
+ # @return [Shale::Adapter::Nokogiri::Node]
16
22
  #
17
23
  # @api private
18
24
  def self.load(xml)
@@ -20,31 +26,36 @@ module Shale
20
26
  config.noblanks
21
27
  end
22
28
 
29
+ unless doc.errors.empty?
30
+ raise ParseError, "Document is invalid: #{doc.errors}"
31
+ end
32
+
23
33
  Node.new(doc.root)
24
34
  end
25
35
 
26
36
  # Serialize Nokogiri document into XML
27
37
  #
28
38
  # @param [::Nokogiri::XML::Document] doc Nokogiri document
29
- # @param [Array<Symbol>] options
39
+ # @param [true, false] pretty
40
+ # @param [true, false] declaration
30
41
  #
31
42
  # @return [String]
32
43
  #
33
44
  # @api private
34
- def self.dump(doc, *options)
45
+ def self.dump(doc, pretty: false, declaration: false)
35
46
  save_with = ::Nokogiri::XML::Node::SaveOptions::AS_XML
36
47
 
37
- if options.include?(:pretty)
48
+ if pretty
38
49
  save_with |= ::Nokogiri::XML::Node::SaveOptions::FORMAT
39
50
  end
40
51
 
41
- unless options.include?(:declaration)
52
+ unless declaration
42
53
  save_with |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
43
54
  end
44
55
 
45
56
  result = doc.to_xml(save_with: save_with)
46
57
 
47
- unless options.include?(:pretty)
58
+ unless pretty
48
59
  result = result.sub(/\n/, '')
49
60
  end
50
61
 
@@ -57,156 +68,6 @@ module Shale
57
68
  def self.create_document
58
69
  Document.new
59
70
  end
60
-
61
- # Wrapper around Nokogiri API
62
- #
63
- # @api private
64
- class Document
65
- # Initialize object
66
- #
67
- # @api private
68
- def initialize
69
- @doc = ::Nokogiri::XML::Document.new
70
- @namespaces = {}
71
- end
72
-
73
- # Return Nokogiri document
74
- #
75
- # @return [::Nokogiri::XML::Document]
76
- #
77
- # @api private
78
- def doc
79
- if @doc.root
80
- @namespaces.each do |prefix, namespace|
81
- @doc.root.add_namespace(prefix, namespace)
82
- end
83
- end
84
-
85
- @doc
86
- end
87
-
88
- # Create Nokogiri element
89
- #
90
- # @param [String] name Name of the XML element
91
- #
92
- # @return [::Nokogiri::XML::Element]
93
- #
94
- # @api private
95
- def create_element(name)
96
- ::Nokogiri::XML::Element.new(name, @doc)
97
- end
98
-
99
- # Add XML namespace to document
100
- #
101
- # @param [String] prefix
102
- # @param [String] namespace
103
- #
104
- # @api private
105
- def add_namespace(prefix, namespace)
106
- @namespaces[prefix] = namespace if prefix && namespace
107
- end
108
-
109
- # Add attribute to Nokogiri element
110
- #
111
- # @param [::Nokogiri::XML::Element] element Nokogiri element
112
- # @param [String] name Name of the XML attribute
113
- # @param [String] value Value of the XML attribute
114
- #
115
- # @api private
116
- def add_attribute(element, name, value)
117
- element[name] = value
118
- end
119
-
120
- # Add child element to Nokogiri element
121
- #
122
- # @param [::Nokogiri::XML::Element] element Nokogiri parent element
123
- # @param [::Nokogiri::XML::Element] child Nokogiri child element
124
- #
125
- # @api private
126
- def add_element(element, child)
127
- element.add_child(child)
128
- end
129
-
130
- # Add text node to Nokogiri element
131
- #
132
- # @param [::Nokogiri::XML::Element] element Nokogiri element
133
- # @param [String] text Text to add
134
- #
135
- # @api private
136
- def add_text(element, text)
137
- element.content = text
138
- end
139
- end
140
-
141
- # Wrapper around Nokogiri::XML::Node API
142
- #
143
- # @api private
144
- class Node
145
- # Initialize object with Nokogiri node
146
- #
147
- # @param [::Nokogiri::XML::Node] node Nokogiri node
148
- #
149
- # @api private
150
- def initialize(node)
151
- @node = node
152
- end
153
-
154
- # Return name of the node in the format of
155
- # namespace:name when the node is namespaced or just name when it's not
156
- #
157
- # @return [String]
158
- #
159
- # @example without namespace
160
- # node.name # => Bar
161
- #
162
- # @example with namespace
163
- # node.name # => http://foo:Bar
164
- #
165
- # @api private
166
- def name
167
- [@node.namespace&.href, @node.name].compact.join(':')
168
- end
169
-
170
- # Return all attributes associated with the node
171
- #
172
- # @return [Hash]
173
- #
174
- # @api private
175
- def attributes
176
- @node.attribute_nodes.each_with_object({}) do |node, hash|
177
- name = [node.namespace&.href, node.name].compact.join(':')
178
- hash[name] = node.value
179
- end
180
- end
181
-
182
- # Return node's element children
183
- #
184
- # @return [Array<Shale::Adapter::Nokogiri::Node>]
185
- #
186
- # @api private
187
- def children
188
- @node
189
- .children
190
- .to_a
191
- .filter(&: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
- first = @node
202
- .children
203
- .to_a
204
- .filter(&:text?)
205
- .first
206
-
207
- first&.text
208
- end
209
- end
210
71
  end
211
72
  end
212
73
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Adapter
5
+ module Ox
6
+ # Wrapper around Ox API
7
+ #
8
+ # @api private
9
+ class Document
10
+ # Return Ox document
11
+ #
12
+ # @return [::Ox::Document]
13
+ #
14
+ # @api private
15
+ attr_reader :doc
16
+
17
+ # Initialize object
18
+ #
19
+ # @api private
20
+ def initialize
21
+ @doc = ::Ox::Document.new
22
+ end
23
+
24
+ # Create Ox element
25
+ #
26
+ # @param [String] name Name of the XML element
27
+ #
28
+ # @return [::Ox::Element]
29
+ #
30
+ # @api private
31
+ def create_element(name)
32
+ ::Ox::Element.new(name)
33
+ end
34
+
35
+ # Create CDATA node and add it to parent
36
+ #
37
+ # @param [String] text
38
+ # @param [::Ox::Element] parent
39
+ #
40
+ # @api private
41
+ def create_cdata(text, parent)
42
+ parent << ::Ox::CData.new(text)
43
+ end
44
+
45
+ # Add XML namespace to document
46
+ #
47
+ # Ox doesn't support XML namespaces so this method does nothing.
48
+ #
49
+ # @param [String] prefix
50
+ # @param [String] namespace
51
+ #
52
+ # @api private
53
+ def add_namespace(prefix, namespace)
54
+ # :noop:
55
+ end
56
+
57
+ # Add attribute to Ox element
58
+ #
59
+ # @param [::Ox::Element] element Ox element
60
+ # @param [String] name Name of the XML attribute
61
+ # @param [String] value Value of the XML attribute
62
+ #
63
+ # @api private
64
+ def add_attribute(element, name, value)
65
+ element[name] = value
66
+ end
67
+
68
+ # Add child element to Ox element
69
+ #
70
+ # @param [::Ox::Element] element Ox parent element
71
+ # @param [::Ox::Element] child Ox child element
72
+ #
73
+ # @api private
74
+ def add_element(element, child)
75
+ element << child
76
+ end
77
+
78
+ # Add text node to Ox element
79
+ #
80
+ # @param [::Ox::Element] element Ox element
81
+ # @param [String] text Text to add
82
+ #
83
+ # @api private
84
+ def add_text(element, text)
85
+ element << text
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Adapter
5
+ module Ox
6
+ # Wrapper around Ox::Element API
7
+ #
8
+ # @api private
9
+ class Node
10
+ # Initialize object with Ox element
11
+ #
12
+ # @param [::Ox::Element] node Ox element
13
+ #
14
+ # @api private
15
+ def initialize(node)
16
+ @node = node
17
+ end
18
+
19
+ # Return namespaces defined on document (Not supported by Ox)
20
+ #
21
+ # @return [Hash<String, String>]
22
+ #
23
+ # @example
24
+ # node.namespaces # => {}
25
+ #
26
+ # @api private
27
+ def namespaces
28
+ {}
29
+ end
30
+
31
+ # Return name of the node in the format of
32
+ # prefix:name when the node is namespaced or just name when it's not
33
+ #
34
+ # @return [String]
35
+ #
36
+ # @example without namespace
37
+ # node.name # => Bar
38
+ #
39
+ # @example with namespace
40
+ # node.name # => foo:Bar
41
+ #
42
+ # @api private
43
+ def name
44
+ @node.name
45
+ end
46
+
47
+ # Return all attributes associated with the node
48
+ #
49
+ # @return [Hash]
50
+ #
51
+ # @api private
52
+ def attributes
53
+ @node.attributes
54
+ end
55
+
56
+ # Return node's parent (Not supported by OX)
57
+ #
58
+ # @return [nil]
59
+ #
60
+ # @api private
61
+ def parent
62
+ # :noop:
63
+ end
64
+
65
+ # Return node's element children
66
+ #
67
+ # @return [Array<Shale::Adapter::Ox::Node>]
68
+ #
69
+ # @api private
70
+ def children
71
+ @node
72
+ .nodes
73
+ .filter { |e| e.is_a?(::Ox::Element) }
74
+ .map { |e| self.class.new(e) }
75
+ end
76
+
77
+ # Return first text child of a node
78
+ #
79
+ # @return [String]
80
+ #
81
+ # @api private
82
+ def text
83
+ texts = @node.nodes.map do |e|
84
+ case e
85
+ when ::Ox::CData
86
+ e.value
87
+ when ::String
88
+ e
89
+ end
90
+ end
91
+
92
+ texts.compact.first
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end