right_cloud_api_base 0.1.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.
- 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
|