right_cloud_api_base 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/HISTORY +2 -0
- data/LICENSE +19 -0
- data/README.md +14 -0
- data/Rakefile +37 -0
- data/lib/base/api_manager.rb +707 -0
- data/lib/base/helpers/cloud_api_logger.rb +214 -0
- data/lib/base/helpers/http_headers.rb +239 -0
- data/lib/base/helpers/http_parent.rb +103 -0
- data/lib/base/helpers/http_request.rb +173 -0
- data/lib/base/helpers/http_response.rb +122 -0
- data/lib/base/helpers/net_http_patch.rb +31 -0
- data/lib/base/helpers/query_api_patterns.rb +862 -0
- data/lib/base/helpers/support.rb +270 -0
- data/lib/base/helpers/support.xml.rb +306 -0
- data/lib/base/helpers/utils.rb +380 -0
- data/lib/base/manager.rb +122 -0
- data/lib/base/parsers/json.rb +38 -0
- data/lib/base/parsers/plain.rb +36 -0
- data/lib/base/parsers/rexml.rb +83 -0
- data/lib/base/parsers/sax.rb +200 -0
- data/lib/base/routines/cache_validator.rb +184 -0
- data/lib/base/routines/connection_proxies/net_http_persistent_proxy.rb +194 -0
- data/lib/base/routines/connection_proxies/right_http_connection_proxy.rb +224 -0
- data/lib/base/routines/connection_proxy.rb +66 -0
- data/lib/base/routines/request_analyzer.rb +122 -0
- data/lib/base/routines/request_generator.rb +48 -0
- data/lib/base/routines/request_initializer.rb +52 -0
- data/lib/base/routines/response_analyzer.rb +152 -0
- data/lib/base/routines/response_parser.rb +79 -0
- data/lib/base/routines/result_wrapper.rb +75 -0
- data/lib/base/routines/retry_manager.rb +106 -0
- data/lib/base/routines/routine.rb +98 -0
- data/lib/right_cloud_api_base.rb +72 -0
- data/lib/right_cloud_api_base_version.rb +37 -0
- data/right_cloud_api_base.gemspec +63 -0
- data/spec/helpers/query_api_pattern_spec.rb +312 -0
- data/spec/helpers/support_spec.rb +211 -0
- data/spec/helpers/support_xml_spec.rb +207 -0
- data/spec/helpers/utils_spec.rb +179 -0
- data/spec/routines/connection_proxies/test_net_http_persistent_proxy_spec.rb +143 -0
- data/spec/routines/test_cache_validator_spec.rb +152 -0
- data/spec/routines/test_connection_proxy_spec.rb +44 -0
- data/spec/routines/test_request_analyzer_spec.rb +106 -0
- data/spec/routines/test_response_analyzer_spec.rb +132 -0
- data/spec/routines/test_response_parser_spec.rb +228 -0
- data/spec/routines/test_result_wrapper_spec.rb +63 -0
- data/spec/routines/test_retry_manager_spec.rb +84 -0
- data/spec/spec_helper.rb +15 -0
- metadata +215 -0
@@ -0,0 +1,270 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class String
|
25
|
+
|
26
|
+
# Constantizes the string.
|
27
|
+
#
|
28
|
+
# @return [Class, Module] The constantized class/module.
|
29
|
+
#
|
30
|
+
# @raise [NameError] If the name is not in CamelCase or is not initialized.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# "Module"._constantize #=> Module
|
34
|
+
# "Class"._constantize #=> Class
|
35
|
+
#
|
36
|
+
def _constantize
|
37
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
|
38
|
+
fail(::NameError, "#{self.inspect} is not a valid constant name!")
|
39
|
+
end
|
40
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Camelizes the string.
|
44
|
+
#
|
45
|
+
# @param [Boolean] lower_case When set to true it downcases the very first symbol of the string.
|
46
|
+
#
|
47
|
+
# @return [String] The camelized string value.
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# 'hello_world'._camelize #=> 'HelloWorld'
|
51
|
+
# 'hello_world'._camelize(true) #=> 'helloWorld'
|
52
|
+
# 'HelloWorld'._camelize #=> 'HelloWorld'
|
53
|
+
#
|
54
|
+
def _camelize(lower_case = false)
|
55
|
+
words = self.gsub(/([A-Z])/, '_\1').
|
56
|
+
split(/_|\b/).
|
57
|
+
map{ |word| word.capitalize }.
|
58
|
+
reject{ |word| word == '' }
|
59
|
+
words[0] = words[0].downcase if words[0] && lower_case
|
60
|
+
words.join('')
|
61
|
+
end
|
62
|
+
alias_method :_camel_case, :_camelize
|
63
|
+
|
64
|
+
# Underscorizes the string.
|
65
|
+
#
|
66
|
+
# @return [String] The camelized string value.
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# 'HelloWorld'._underscore #=> 'hello_world'
|
70
|
+
#
|
71
|
+
def _snake_case
|
72
|
+
self.split(/\b/).
|
73
|
+
map{ |word| word.gsub(/[A-Z]/){ |match| "#{$`=='' ? '' : '_'}#{match.downcase}" } }.
|
74
|
+
join('')
|
75
|
+
end
|
76
|
+
alias_method :_underscore, :_snake_case
|
77
|
+
|
78
|
+
# Wraps the string into an array.
|
79
|
+
#
|
80
|
+
# @return [Array]
|
81
|
+
#
|
82
|
+
# @example
|
83
|
+
# 'hahaha'._arrayify #=> ['hahaha']
|
84
|
+
#
|
85
|
+
def _arrayify
|
86
|
+
[ self ]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns +true+ is the string has zero length or contains spaces only. And it returns +false+
|
90
|
+
# if the string has any meaningful value.
|
91
|
+
#
|
92
|
+
# @return [Boolean]
|
93
|
+
#
|
94
|
+
def _blank?
|
95
|
+
empty? || strip.empty?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
class Object
|
101
|
+
|
102
|
+
# Checks if the current object is blank or empty.
|
103
|
+
# "", " ", nil, [] and {} are assumes as blank.
|
104
|
+
#
|
105
|
+
# @return [Boolean] +True+ if the object is blank and +false+ otherwise.
|
106
|
+
#
|
107
|
+
def _blank?
|
108
|
+
case
|
109
|
+
when respond_to?(:blank?) then blank?
|
110
|
+
when respond_to?(:empty?) then empty?
|
111
|
+
else !self
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Checks if the object has any non-blank value (opposite to Object#_blank?)
|
116
|
+
#
|
117
|
+
# @return [Boolean] +True+ if the object has any meaningful value and +false+ otherwise.
|
118
|
+
#
|
119
|
+
def _present?
|
120
|
+
!_blank?
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns a list of modules an object is extended with.
|
124
|
+
#
|
125
|
+
# @return [Array] A list of modules.
|
126
|
+
#
|
127
|
+
def _extended
|
128
|
+
(class << self; self; end).included_modules
|
129
|
+
end
|
130
|
+
|
131
|
+
# Checks whether an object was extended with a module.
|
132
|
+
#
|
133
|
+
# @return [Boolean] +True+ if the object is extended with the given module.
|
134
|
+
#
|
135
|
+
def _extended?(_module)
|
136
|
+
_extended.include?(_module)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Wraps the object into an array.
|
140
|
+
#
|
141
|
+
# @return [Array]
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
# nil._arrayify #=> []
|
145
|
+
# 1._arrayify #=> [1]
|
146
|
+
# :sym._arrayify #=> [:sym]
|
147
|
+
#
|
148
|
+
def _arrayify
|
149
|
+
Array(self)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Array
|
154
|
+
|
155
|
+
# Stringifies keys on all the hash items.
|
156
|
+
#
|
157
|
+
def _symbolize_keys
|
158
|
+
map do |item|
|
159
|
+
item.respond_to?(:_symbolize_keys) ? item._symbolize_keys : item
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Stringifies keys on all the hash items.
|
164
|
+
#
|
165
|
+
def _stringify_keys
|
166
|
+
map do |item|
|
167
|
+
item.respond_to?(:_stringify_keys) ? item._stringify_keys : item
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class Hash
|
173
|
+
|
174
|
+
# Converts the root keys of the hash to symbols.
|
175
|
+
#
|
176
|
+
# @return [Hash]
|
177
|
+
#
|
178
|
+
def _symbolize_keys
|
179
|
+
inject({}) do |hash, (key, value)|
|
180
|
+
new_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
181
|
+
value = value._symbolize_keys if value.respond_to?(:_symbolize_keys)
|
182
|
+
hash[new_key] = value
|
183
|
+
hash
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Converts the keys of the hash to strings.
|
188
|
+
#
|
189
|
+
# @return [Hash]
|
190
|
+
#
|
191
|
+
def _stringify_keys
|
192
|
+
inject({}) do |hash, (key, value)|
|
193
|
+
new_key = key.to_s if key.respond_to?(:to_s)
|
194
|
+
value = value._stringify_keys if value.respond_to?(:_stringify_keys)
|
195
|
+
hash[new_key] = value
|
196
|
+
hash
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Extract a value from the hash by its path. The path is a comma-separated list of keys, staring
|
201
|
+
# from the root key.
|
202
|
+
#
|
203
|
+
# @param [Array] path The path to the key. If the very last value is a hash then it is treated as
|
204
|
+
# a set of options.
|
205
|
+
#
|
206
|
+
# The options are:
|
207
|
+
# - :arrayify Convert the result into Array (unless it is).
|
208
|
+
# - :default A value to be returned unless the requested key exist.
|
209
|
+
#
|
210
|
+
# @yield [] If a block is given and the key is not found then it calls the block.
|
211
|
+
# @yieldreturn [Object] he block may raise a custom exception or return anything. The returned
|
212
|
+
# value it used for the method return.
|
213
|
+
#
|
214
|
+
# @return [Object] Whatever value the requested key has or the default value.
|
215
|
+
#
|
216
|
+
# @example
|
217
|
+
# {}._at('x','y') #=> Item at "x"->"y" is not found or not a Hash instance (RuntimeError)
|
218
|
+
# {}._at('x', :default => 'defval') #=> 'defval'
|
219
|
+
# {}._at('x'){ 'defval' } #=> 'defval'
|
220
|
+
# {}._at('x'){ fail "NotFound.MyCoolError" } #=> NotFound.MyCoolError (RuntimeError)
|
221
|
+
# {'x' => nil}._at('x') #=> nil
|
222
|
+
# {'x' => 4}._at('x') #=> 4
|
223
|
+
# {'x' => { 'y' => { 'z' => 'value'} } }._at('x', 'y', 'z') #=> 'value'
|
224
|
+
# {'x' => { 'y' => { 'z' => 'value'} } }._at('x', 'y', 'z', :arrayify => true) #=> ['value']
|
225
|
+
#
|
226
|
+
def _at(*path, &block)
|
227
|
+
path = path.flatten
|
228
|
+
options = path.last.is_a?(Hash) ? path.pop.dup : {}
|
229
|
+
key = path.shift
|
230
|
+
(options[:path] ||= []) << key
|
231
|
+
if key?(key)
|
232
|
+
if path._blank?
|
233
|
+
# We have reached the final key in the list - report it back.
|
234
|
+
return options[:arrayify] ? self[key]._arrayify : self[key]
|
235
|
+
end
|
236
|
+
return self[key]._at(path << options, &block) if self[key].is_a?(Hash)
|
237
|
+
end
|
238
|
+
return options[:default] if options.key?(:default)
|
239
|
+
return block.call if block
|
240
|
+
fail(StandardError.new("Item at #{options[:path].map{|i| i.inspect}.join('->')} is not found or not a Hash instance"))
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
# Extracts a value from the hash by its path and arrayifies it.
|
245
|
+
#
|
246
|
+
# @param [Array] path The path to the key. If the very last value is a hash then it is treated as
|
247
|
+
# a set of options.
|
248
|
+
#
|
249
|
+
# @return [Array] Single item array with whatever value the requested key has.
|
250
|
+
#
|
251
|
+
# @example
|
252
|
+
# {}._arrayify_at('x', 'y', 'z') #=> []
|
253
|
+
# { 'x' => { 'y' => { 'z' => 'value'} }}._arrayify_at('x', 'y', 'z') #=> ['value']
|
254
|
+
#
|
255
|
+
#
|
256
|
+
def _arrayify_at(*path)
|
257
|
+
_at(path << { :arrayify => true, :default => [] })
|
258
|
+
end
|
259
|
+
|
260
|
+
# Wraps the hash into an array.
|
261
|
+
#
|
262
|
+
# @return [Array]
|
263
|
+
#
|
264
|
+
# @example
|
265
|
+
# {1 => 2}._arrayify #=> [{1 => 2}]
|
266
|
+
#
|
267
|
+
def _arrayify
|
268
|
+
[ self ]
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,306 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class Object #:nodoc:
|
25
|
+
|
26
|
+
RIGHTXMLSUPPORT_XMLESCAPE = {'"' => '"', '\'' =>''', '<' => '<', '>' => '>'}
|
27
|
+
RIGHTXMLSUPPORT_XMLUNESCAPE = RIGHTXMLSUPPORT_XMLESCAPE.invert
|
28
|
+
RIGHTXMLSUPPORT_XMLINDENT = ""
|
29
|
+
RIGHTXMLSUPPORT_XMLLEVEL = 0
|
30
|
+
RIGHTXMLSUPPORT_XMLCRLF = "\n"
|
31
|
+
|
32
|
+
# Escapes non-XML symbols.
|
33
|
+
#
|
34
|
+
# @return [String] XML-escaped string.
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# "Hello <'world'> & \"the Universe\""._xml_escape #=>
|
38
|
+
# "Hello <'world'> & "the Universe""
|
39
|
+
#
|
40
|
+
def _xml_escape
|
41
|
+
self.to_s.gsub('&', '&').gsub(/#{RIGHTXMLSUPPORT_XMLESCAPE.keys.join('|')}/) { |match| RIGHTXMLSUPPORT_XMLESCAPE[match] }
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Conditionally escapes non-XML symbols.
|
46
|
+
#
|
47
|
+
# @param [Hash] opts A set of options.
|
48
|
+
# @option opts [Boolean] :escape The flag.
|
49
|
+
#
|
50
|
+
# @return [String] XML-escaped string if :escape it set ot true or self otherwise.
|
51
|
+
#
|
52
|
+
def _xml_conditional_escape(opts={})
|
53
|
+
opts[:escape] ? self._xml_escape : self.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
# Unescapes XML-escaped symbols.
|
57
|
+
#
|
58
|
+
# @return [String] XML-unscaped string.
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# "Hello <'world'> & "the Universe""._xml_unescape #=>
|
62
|
+
# "Hello <'world'> & \"the Universe\"
|
63
|
+
#
|
64
|
+
def _xml_unescape
|
65
|
+
self.to_s.gsub(/#{RIGHTXMLSUPPORT_XMLUNESCAPE.keys.join('|')}/) { |match| RIGHTXMLSUPPORT_XMLUNESCAPE[match] }.gsub('&','&')
|
66
|
+
end
|
67
|
+
|
68
|
+
# Fixes the given set of options.
|
69
|
+
#
|
70
|
+
def _xml_get_opts(opts={}) # :nodoc:
|
71
|
+
opts[:level] ||= RIGHTXMLSUPPORT_XMLLEVEL
|
72
|
+
opts[:indent] ||= RIGHTXMLSUPPORT_XMLINDENT
|
73
|
+
opts[:crlf] ||= opts[:indent].empty? ? "" : RIGHTXMLSUPPORT_XMLCRLF
|
74
|
+
opts
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns an aligned piece of XML text.
|
78
|
+
#
|
79
|
+
def _xml_align(opts={}) # :nodoc:
|
80
|
+
return '' if self.to_s.empty?
|
81
|
+
opts = _xml_get_opts(opts)
|
82
|
+
"#{opts[:indent]*opts[:level]}#{self}#{opts[:crlf]}"
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Returns an XML-representation of the object.
|
87
|
+
#
|
88
|
+
# @param [Hash] opts A set of options.
|
89
|
+
# @option opts [Boolean] :escape The flag.
|
90
|
+
#
|
91
|
+
# @return [String] The result is an XML-escaped string (if :escape flag is set) or self otherwise.
|
92
|
+
#
|
93
|
+
def _to_xml(opts={})
|
94
|
+
_xml_conditional_escape(_xml_get_opts(opts))
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns an XML-representation of the object starting with '<?xml version="1.0" encoding="UTF-8"?>'
|
98
|
+
# string.
|
99
|
+
#
|
100
|
+
# @param [Hash] args A set of arguments (see _to_xml)
|
101
|
+
#
|
102
|
+
# @return [String]
|
103
|
+
#
|
104
|
+
def _to_xml!(*args)
|
105
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
|
106
|
+
"#{_to_xml(*args)}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# --- Array ---
|
111
|
+
|
112
|
+
class Array #:nodoc:
|
113
|
+
|
114
|
+
# Returns an XML-representation if the array object.
|
115
|
+
#
|
116
|
+
# @param [Hash] opts A set of options.
|
117
|
+
# @option opts [Boolean] :escape The flag.
|
118
|
+
# @option opts [String] :tag The tag every array item is to be wrapped with ('<item>' by default)
|
119
|
+
#
|
120
|
+
# @return [String]
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
# [1,2,3,4]._to_xml #=>
|
124
|
+
# '<item>1</item><item>2</item><item>3</item><item>4</item>'
|
125
|
+
#
|
126
|
+
# @example
|
127
|
+
# [1,2,3,4]._to_xml(:crlf => "\n") #=>
|
128
|
+
# <item>1</item>
|
129
|
+
# <item>2</item>
|
130
|
+
# <item>3</item>
|
131
|
+
# <item>4</item>
|
132
|
+
#
|
133
|
+
# @example
|
134
|
+
# [1,2,[3,4,[5]]]._to_xml(:indent => ' ', :tag => 'hoho') #=>
|
135
|
+
# <hoho>1</hoho>
|
136
|
+
# <hoho>2</hoho>
|
137
|
+
# <hoho>
|
138
|
+
# <item>3</item>
|
139
|
+
# <item>4</item>
|
140
|
+
# <item>
|
141
|
+
# <item>5</item>
|
142
|
+
# </item>
|
143
|
+
# </hoho>
|
144
|
+
#
|
145
|
+
def _to_xml(opts={})
|
146
|
+
opts = _xml_get_opts(opts)
|
147
|
+
tag = opts.delete(:tag) || 'item'
|
148
|
+
{ tag => self }._to_xml(opts)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Hash #:nodoc:
|
153
|
+
RIGHTXMLSUPPORT_SORTORDERREGEXP = /(\{#(\d+)\})$/
|
154
|
+
|
155
|
+
# Generate a consecutive id for a new key.
|
156
|
+
# If String or Symbol is passed then adds the id to it.
|
157
|
+
#
|
158
|
+
# The method is widely used for MS Azure XMLs because MS requires XML
|
159
|
+
# tags to appear in a predefined order. Grrr... ;)
|
160
|
+
#
|
161
|
+
# @param [String] key_name Usually a tag name.
|
162
|
+
#
|
163
|
+
# @return [String] A string containing the original one and the current ordering ID.
|
164
|
+
# if key_name was not set then it returns the next id value.
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
# Hash::_order('hahaha') #=> "hahaha{#1}"
|
168
|
+
# Hash::_order('hohoho') #=> "hohoho{#2}"
|
169
|
+
# Hash::_order #=> 3
|
170
|
+
#
|
171
|
+
# @example
|
172
|
+
# hash = {
|
173
|
+
# Hash::_order('foo') => 34,
|
174
|
+
# Hash::_order('boo') => 45,
|
175
|
+
# Hash::_order('zoo') => 53,
|
176
|
+
# Hash::_order('poo') => 10,
|
177
|
+
# Hash::_order('moo') => {
|
178
|
+
# Hash::_order('noo') => 101,
|
179
|
+
# Hash::_order('too') => 113,
|
180
|
+
# Hash::_order('koo') => 102,
|
181
|
+
# },
|
182
|
+
# Hash::_order('woo') => 03,
|
183
|
+
# Hash::_order('hoo') => 1
|
184
|
+
# }
|
185
|
+
# hash._to_xml(:indent => ' ') #=>
|
186
|
+
# <boo>45</boo>
|
187
|
+
# <zoo>53</zoo>
|
188
|
+
# <poo>10</poo>
|
189
|
+
# <moo>
|
190
|
+
# <noo>101</noo>
|
191
|
+
# <too>113</too>
|
192
|
+
# <koo>102</koo>
|
193
|
+
# </moo>
|
194
|
+
# <woo>3</woo>
|
195
|
+
# <hoo>1</hoo>
|
196
|
+
#
|
197
|
+
def self._order(key_name=nil)
|
198
|
+
@_next_ordered_key_id ||= 0
|
199
|
+
@_next_ordered_key_id += 1
|
200
|
+
if key_name
|
201
|
+
fail(RuntimeError.new('String or Symbol is expected')) unless key_name.is_a?(String) || key_name.is_a?(Symbol)
|
202
|
+
result = "#{key_name}{##{@_next_ordered_key_id}}"
|
203
|
+
result = result.to_sym if key_name.is_a?(Symbol)
|
204
|
+
result
|
205
|
+
else
|
206
|
+
@_next_ordered_key_id
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Sorts the keys accordingly to their order definition (if Hash::_order was used).
|
211
|
+
def _xml_sort_keys # :nodoc:
|
212
|
+
keys.sort do |key1, key2|
|
213
|
+
key1idx = key1.to_s[RIGHTXMLSUPPORT_SORTORDERREGEXP] && $2 && $2.to_i
|
214
|
+
key2idx = key2.to_s[RIGHTXMLSUPPORT_SORTORDERREGEXP] && $2 && $2.to_i
|
215
|
+
if key1idx && key2idx then key1idx <=> key2idx
|
216
|
+
elsif key1idx then -1
|
217
|
+
elsif key2idx then 1
|
218
|
+
else 0
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Builds the final XML tag text.
|
224
|
+
def _xml_finalize_tag(tag_name, tag_attributes, tag_text, tag_elements, opts) # :nodoc:
|
225
|
+
next_opts = opts.merge(:level => opts[:level] + 1)
|
226
|
+
case
|
227
|
+
when tag_elements.empty? && tag_text.empty? then "<#{tag_name}#{tag_attributes}/>"._xml_align(opts)
|
228
|
+
when tag_elements.empty? then "<#{tag_name}#{tag_attributes}>#{tag_text}</#{tag_name}>"._xml_align(opts)
|
229
|
+
else "<#{tag_name}#{tag_attributes}>"._xml_align(opts) +
|
230
|
+
tag_text._xml_align(next_opts) +
|
231
|
+
tag_elements +
|
232
|
+
"</#{tag_name}>"._xml_align(opts)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Returns an XML-representation if the hash object.
|
237
|
+
#
|
238
|
+
# @param [Hash] opts A set of options.
|
239
|
+
# @option opts [Boolean] :escape The flag.
|
240
|
+
# @option opts [Boolean] :indent The indentation string (is blank by default).
|
241
|
+
# @option opts [Boolean] :crfl The CR/LF string (is blank by default).
|
242
|
+
#
|
243
|
+
# @return [String]
|
244
|
+
#
|
245
|
+
# @example
|
246
|
+
# ({ 'a' => [ 1, { :c => 'd' } ] })._to_xml #=>
|
247
|
+
# "<a>1</a><a><c>d</c></a>"
|
248
|
+
#
|
249
|
+
# @example
|
250
|
+
# { 'screen' => {
|
251
|
+
# '@width' => 1080,
|
252
|
+
# '@hight' => 720,
|
253
|
+
# '@@text' => 'HD',
|
254
|
+
# 'color' => {
|
255
|
+
# '@max-colors' => 65535,
|
256
|
+
# '@dinamic-resolution' => '1:1000000',
|
257
|
+
# '@@text' => '<"PAL">',
|
258
|
+
# 'brightness' => {
|
259
|
+
# 'bright' => true
|
260
|
+
# }
|
261
|
+
# }
|
262
|
+
# }
|
263
|
+
# }._to_xml(:indent => ' ',
|
264
|
+
# :escape => true) #=>
|
265
|
+
# <screen width="1080" hight="720">
|
266
|
+
# HD
|
267
|
+
# <color max-colors="65535" dinamic-resolution="1:1000000">
|
268
|
+
# <"PAL">
|
269
|
+
# <brightness>
|
270
|
+
# <bright>true</bright>
|
271
|
+
# </brightness>
|
272
|
+
# </color>
|
273
|
+
# </screen>
|
274
|
+
#
|
275
|
+
def _to_xml(opts={})
|
276
|
+
result = ''
|
277
|
+
opts = _xml_get_opts(opts)
|
278
|
+
next_opts = opts.merge(:level => opts[:level] + 1)
|
279
|
+
_xml_sort_keys.each do |tag_name|
|
280
|
+
value = self[tag_name]
|
281
|
+
tag_name = tag_name.to_s.sub(RIGHTXMLSUPPORT_SORTORDERREGEXP, '')
|
282
|
+
if value.is_a?(Hash)
|
283
|
+
tag_attributes = ''; tag_elements = ''; tag_text = ''
|
284
|
+
value._xml_sort_keys.each do |item|
|
285
|
+
item_value = value[item]
|
286
|
+
item = item.to_s.sub(RIGHTXMLSUPPORT_SORTORDERREGEXP, '')
|
287
|
+
case
|
288
|
+
when item == '@@text' then tag_text << item_value._xml_conditional_escape(opts)
|
289
|
+
when item[/^@[^@]/] then tag_attributes << %Q{ #{item[1..-1]}="#{item_value._xml_conditional_escape(opts)}"}
|
290
|
+
else tag_elements << { item => item_value }._to_xml(next_opts)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
result << _xml_finalize_tag(tag_name, tag_attributes, tag_text, tag_elements, opts)
|
294
|
+
elsif value.is_a?(Array)
|
295
|
+
value.each do |item|
|
296
|
+
item = { tag_name => item } if item.is_a?(Array)
|
297
|
+
result << { tag_name => item }._to_xml(opts)
|
298
|
+
end
|
299
|
+
else
|
300
|
+
result << _xml_finalize_tag(tag_name, '', value.to_s, '', opts)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
result
|
304
|
+
end
|
305
|
+
|
306
|
+
end
|