mu 5.7.8 → 5.7.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (198) hide show
  1. data/lib/mu.rb +1 -1
  2. data/lib/mu/command.rb +18 -2
  3. data/lib/mu/command/cmd_musl.rb +165 -0
  4. data/lib/mu/command/cmd_runscale.rb +12 -8
  5. data/lib/mu/command/help.rb +1 -0
  6. data/lib/mu/har.rb +449 -0
  7. data/lib/mu/libxml.rb +21 -0
  8. data/lib/mu/maker.rb +288 -0
  9. data/lib/mu/xmlizable.rb +559 -0
  10. metadata +78 -267
  11. data/rdoc/Gemfile.html +0 -194
  12. data/rdoc/LICENSE_txt.html +0 -201
  13. data/rdoc/Mu.html +0 -478
  14. data/rdoc/Mu/Client.html +0 -461
  15. data/rdoc/Mu/Command.html +0 -338
  16. data/rdoc/Mu/Command/API.html +0 -844
  17. data/rdoc/Mu/Command/Cmd_appid.html +0 -1341
  18. data/rdoc/Mu/Command/Cmd_cli.html +0 -800
  19. data/rdoc/Mu/Command/Cmd_ddt.html +0 -1612
  20. data/rdoc/Mu/Command/Cmd_homepage.html +0 -762
  21. data/rdoc/Mu/Command/Cmd_muapi.html +0 -1363
  22. data/rdoc/Mu/Command/Cmd_netconfig.html +0 -1077
  23. data/rdoc/Mu/Command/Cmd_runscale.html +0 -1441
  24. data/rdoc/Mu/Command/Cmd_runscenario.html +0 -787
  25. data/rdoc/Mu/Command/Cmd_runverify.html +0 -893
  26. data/rdoc/Mu/Command/Cmd_scale.html +0 -1323
  27. data/rdoc/Mu/Command/Cmd_system.html +0 -677
  28. data/rdoc/Mu/Command/Curl.html +0 -751
  29. data/rdoc/Mu/Command/Help.html +0 -305
  30. data/rdoc/Mu/Curl.html +0 -243
  31. data/rdoc/Mu/Curl/Error.html +0 -304
  32. data/rdoc/Mu/Curl/Error/Authorize.html +0 -355
  33. data/rdoc/Mu/Curl/Error/Connect.html +0 -286
  34. data/rdoc/Mu/Curl/Error/DNS.html +0 -236
  35. data/rdoc/Mu/Curl/Error/Region.html +0 -307
  36. data/rdoc/Mu/Curl/Error/Status.html +0 -286
  37. data/rdoc/Mu/Curl/Error/Timeout.html +0 -286
  38. data/rdoc/Mu/Curl/Verify.html +0 -472
  39. data/rdoc/Mu/Curl/Verify/Request.html +0 -424
  40. data/rdoc/Mu/Curl/Verify/Response.html +0 -372
  41. data/rdoc/Mu/Curl/Verify/Result.html +0 -373
  42. data/rdoc/Mu/Ddt.html +0 -1347
  43. data/rdoc/Mu/Helper.html +0 -517
  44. data/rdoc/Mu/Homepage.html +0 -719
  45. data/rdoc/Mu/HttpHelper.html +0 -890
  46. data/rdoc/Mu/Muapi.html +0 -1226
  47. data/rdoc/Mu/Netconfig.html +0 -1178
  48. data/rdoc/Mu/Scale.html +0 -1359
  49. data/rdoc/Mu/System.html +0 -504
  50. data/rdoc/Object.html +0 -285
  51. data/rdoc/README_rdoc.html +0 -892
  52. data/rdoc/Rakefile.html +0 -257
  53. data/rdoc/TCTestMu.html +0 -2583
  54. data/rdoc/Test.html +0 -235
  55. data/rdoc/Test/Unit.html +0 -235
  56. data/rdoc/Test/Unit/TestCase.html +0 -236
  57. data/rdoc/Test/helper_rb.html +0 -62
  58. data/rdoc/Test/tc_test_mu_rb.html +0 -60
  59. data/rdoc/VERSION.html +0 -179
  60. data/rdoc/bin/mu.html +0 -54
  61. data/rdoc/classes/Mu.html +0 -132
  62. data/rdoc/classes/Mu/Client.html +0 -214
  63. data/rdoc/classes/Mu/Command.html +0 -129
  64. data/rdoc/classes/Mu/Command/API.html +0 -362
  65. data/rdoc/classes/Mu/Command/Cmd_appid.html +0 -238
  66. data/rdoc/classes/Mu/Command/Cmd_cli.html +0 -273
  67. data/rdoc/classes/Mu/Command/Cmd_ddt.html +0 -639
  68. data/rdoc/classes/Mu/Command/Cmd_homepage.html +0 -276
  69. data/rdoc/classes/Mu/Command/Cmd_muapi.html +0 -527
  70. data/rdoc/classes/Mu/Command/Cmd_netconfig.html +0 -399
  71. data/rdoc/classes/Mu/Command/Cmd_runscale.html +0 -220
  72. data/rdoc/classes/Mu/Command/Cmd_runscenario.html +0 -197
  73. data/rdoc/classes/Mu/Command/Cmd_runverify.html +0 -196
  74. data/rdoc/classes/Mu/Command/Cmd_scale.html +0 -511
  75. data/rdoc/classes/Mu/Command/Cmd_system.html +0 -236
  76. data/rdoc/classes/Mu/Command/Curl.html +0 -182
  77. data/rdoc/classes/Mu/Command/Help.html +0 -137
  78. data/rdoc/classes/Mu/Curl.html +0 -116
  79. data/rdoc/classes/Mu/Curl/Error.html +0 -148
  80. data/rdoc/classes/Mu/Curl/Error/Authorize.html +0 -165
  81. data/rdoc/classes/Mu/Curl/Error/Connect.html +0 -139
  82. data/rdoc/classes/Mu/Curl/Error/DNS.html +0 -113
  83. data/rdoc/classes/Mu/Curl/Error/Region.html +0 -150
  84. data/rdoc/classes/Mu/Curl/Error/Status.html +0 -139
  85. data/rdoc/classes/Mu/Curl/Error/Timeout.html +0 -139
  86. data/rdoc/classes/Mu/Curl/Verify.html +0 -207
  87. data/rdoc/classes/Mu/Curl/Verify/Request.html +0 -187
  88. data/rdoc/classes/Mu/Curl/Verify/Response.html +0 -172
  89. data/rdoc/classes/Mu/Curl/Verify/Result.html +0 -172
  90. data/rdoc/classes/Mu/Ddt.html +0 -610
  91. data/rdoc/classes/Mu/Helper.html +0 -308
  92. data/rdoc/classes/Mu/Homepage.html +0 -306
  93. data/rdoc/classes/Mu/HttpHelper.html +0 -393
  94. data/rdoc/classes/Mu/Muapi.html +0 -549
  95. data/rdoc/classes/Mu/Netconfig.html +0 -478
  96. data/rdoc/classes/Mu/Scale.html +0 -580
  97. data/rdoc/classes/Mu/System.html +0 -232
  98. data/rdoc/classes/Object.html +0 -139
  99. data/rdoc/classes/TCTestMu.html +0 -948
  100. data/rdoc/classes/Test.html +0 -107
  101. data/rdoc/classes/Test/Unit.html +0 -107
  102. data/rdoc/classes/Test/Unit/TestCase.html +0 -113
  103. data/rdoc/created.rid +0 -36
  104. data/rdoc/files/lib/mu/api/ddt_rb.html +0 -101
  105. data/rdoc/files/lib/mu/api/homepage_rb.html +0 -101
  106. data/rdoc/files/lib/mu/api/muapi_rb.html +0 -101
  107. data/rdoc/files/lib/mu/api/netconfig_rb.html +0 -101
  108. data/rdoc/files/lib/mu/api/scale_rb.html +0 -101
  109. data/rdoc/files/lib/mu/api/system_rb.html +0 -101
  110. data/rdoc/files/lib/mu/client_rb.html +0 -101
  111. data/rdoc/files/lib/mu/command/api_rb.html +0 -101
  112. data/rdoc/files/lib/mu/command/cmd_appid_rb.html +0 -119
  113. data/rdoc/files/lib/mu/command/cmd_cli_rb.html +0 -108
  114. data/rdoc/files/lib/mu/command/cmd_ddt_rb.html +0 -116
  115. data/rdoc/files/lib/mu/command/cmd_homepage_rb.html +0 -115
  116. data/rdoc/files/lib/mu/command/cmd_muapi_rb.html +0 -115
  117. data/rdoc/files/lib/mu/command/cmd_netconfig_rb.html +0 -116
  118. data/rdoc/files/lib/mu/command/cmd_runscale_rb.html +0 -119
  119. data/rdoc/files/lib/mu/command/cmd_runscenario_rb.html +0 -115
  120. data/rdoc/files/lib/mu/command/cmd_runverify_rb.html +0 -118
  121. data/rdoc/files/lib/mu/command/cmd_scale_rb.html +0 -115
  122. data/rdoc/files/lib/mu/command/cmd_system_rb.html +0 -116
  123. data/rdoc/files/lib/mu/command/curl_rb.html +0 -101
  124. data/rdoc/files/lib/mu/command/help_rb.html +0 -101
  125. data/rdoc/files/lib/mu/command_rb.html +0 -107
  126. data/rdoc/files/lib/mu/curl/error_rb.html +0 -101
  127. data/rdoc/files/lib/mu/curl/verify_rb.html +0 -101
  128. data/rdoc/files/lib/mu/helper_rb.html +0 -101
  129. data/rdoc/files/lib/mu/http_helper_rb.html +0 -101
  130. data/rdoc/files/lib/mu_rb.html +0 -121
  131. data/rdoc/files/test/helper_rb.html +0 -112
  132. data/rdoc/files/test/tc_test_mu_rb.html +0 -111
  133. data/rdoc/fr_class_index.html +0 -34
  134. data/rdoc/fr_file_index.html +0 -31
  135. data/rdoc/fr_method_index.html +0 -112
  136. data/rdoc/images/brick.png +0 -0
  137. data/rdoc/images/brick_link.png +0 -0
  138. data/rdoc/images/bug.png +0 -0
  139. data/rdoc/images/bullet_black.png +0 -0
  140. data/rdoc/images/bullet_toggle_minus.png +0 -0
  141. data/rdoc/images/bullet_toggle_plus.png +0 -0
  142. data/rdoc/images/date.png +0 -0
  143. data/rdoc/images/find.png +0 -0
  144. data/rdoc/images/loadingAnimation.gif +0 -0
  145. data/rdoc/images/macFFBgHack.png +0 -0
  146. data/rdoc/images/package.png +0 -0
  147. data/rdoc/images/page_green.png +0 -0
  148. data/rdoc/images/page_white_text.png +0 -0
  149. data/rdoc/images/page_white_width.png +0 -0
  150. data/rdoc/images/plugin.png +0 -0
  151. data/rdoc/images/ruby.png +0 -0
  152. data/rdoc/images/tag_green.png +0 -0
  153. data/rdoc/images/wrench.png +0 -0
  154. data/rdoc/images/wrench_orange.png +0 -0
  155. data/rdoc/images/zoom.png +0 -0
  156. data/rdoc/index.html +0 -884
  157. data/rdoc/js/darkfish.js +0 -116
  158. data/rdoc/js/jquery.js +0 -32
  159. data/rdoc/js/quicksearch.js +0 -114
  160. data/rdoc/js/thickbox-compressed.js +0 -10
  161. data/rdoc/lib/mu/api/ddt_rb.html +0 -52
  162. data/rdoc/lib/mu/api/homepage_rb.html +0 -52
  163. data/rdoc/lib/mu/api/muapi_rb.html +0 -52
  164. data/rdoc/lib/mu/api/netconfig_rb.html +0 -52
  165. data/rdoc/lib/mu/api/scale_rb.html +0 -52
  166. data/rdoc/lib/mu/api/system_rb.html +0 -52
  167. data/rdoc/lib/mu/client_rb.html +0 -52
  168. data/rdoc/lib/mu/command/api_rb.html +0 -52
  169. data/rdoc/lib/mu/command/cmd_appid_rb.html +0 -62
  170. data/rdoc/lib/mu/command/cmd_cli_rb.html +0 -55
  171. data/rdoc/lib/mu/command/cmd_ddt_rb.html +0 -60
  172. data/rdoc/lib/mu/command/cmd_homepage_rb.html +0 -57
  173. data/rdoc/lib/mu/command/cmd_muapi_rb.html +0 -59
  174. data/rdoc/lib/mu/command/cmd_netconfig_rb.html +0 -58
  175. data/rdoc/lib/mu/command/cmd_runscale_rb.html +0 -62
  176. data/rdoc/lib/mu/command/cmd_runscenario_rb.html +0 -57
  177. data/rdoc/lib/mu/command/cmd_runverify_rb.html +0 -61
  178. data/rdoc/lib/mu/command/cmd_scale_rb.html +0 -58
  179. data/rdoc/lib/mu/command/cmd_system_rb.html +0 -59
  180. data/rdoc/lib/mu/command/curl_rb.html +0 -52
  181. data/rdoc/lib/mu/command/help_rb.html +0 -52
  182. data/rdoc/lib/mu/command_rb.html +0 -55
  183. data/rdoc/lib/mu/curl/error_rb.html +0 -52
  184. data/rdoc/lib/mu/curl/verify_rb.html +0 -52
  185. data/rdoc/lib/mu/helper_rb.html +0 -52
  186. data/rdoc/lib/mu/http_helper_rb.html +0 -52
  187. data/rdoc/lib/mu_rb.html +0 -80
  188. data/rdoc/rdoc-style.css +0 -208
  189. data/rdoc/rdoc.css +0 -706
  190. data/test/data/app_id_stats.csv +0 -1
  191. data/test/data/app_id_status.json +0 -156
  192. data/test/data/data_cgi.msl +0 -94
  193. data/test/data/data_cgi.xml +0 -322
  194. data/test/data/default_test.csv +0 -3
  195. data/test/data/ftp_with_channel.xml +0 -1643
  196. data/test/data/irc.xml +0 -3837
  197. data/test/data/scale.json +0 -25
  198. data/test/data/test_data_cgi_error.xml +0 -35
data/lib/mu/libxml.rb ADDED
@@ -0,0 +1,21 @@
1
+ # Copyright (C) 2008 Mu Dynamics, Inc
2
+ #
3
+ # This program is confidential and proprietary to Mu Dynamics, Inc and
4
+ # may not be reproduced, published or disclosed to others without its
5
+ # authorization.
6
+
7
+ module LibXML
8
+ module XML
9
+ class Node
10
+ # Get first element, optionally with name.
11
+ def first_element name=nil
12
+ each_element do |element|
13
+ if not name or name == element.name
14
+ return element
15
+ end
16
+ end
17
+ return nil
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/mu/maker.rb ADDED
@@ -0,0 +1,288 @@
1
+ module MuSL
2
+ # A simple DSL for making MuSL code. Invoke thusly and provided nested
3
+ # blocks to construct the scenario:
4
+ # MuSL::Maker.create do |scenario|
5
+ # scenario.hosts do |hosts|
6
+ # ...
7
+ # end
8
+ # end
9
+ class Maker
10
+ def self.create name='scenario'
11
+ scenario = Scenario.new
12
+ scenario.create(name) do
13
+ yield scenario
14
+ end
15
+ return scenario.musl.join
16
+ end
17
+
18
+ class Scenario
19
+ def depth
20
+ @depth ||= 0
21
+ end
22
+
23
+ def musl
24
+ @musl ||= []
25
+ end
26
+
27
+ def indent amount
28
+ " " * (4 * amount)
29
+ end
30
+
31
+ def line text=''
32
+ musl << indent(depth) + text + "\r\n"
33
+ end
34
+
35
+ def comment text
36
+ line '# ' + text
37
+ end
38
+
39
+ def literal text
40
+ musl << indent(depth) + text + "\r\n"
41
+ end
42
+
43
+ def literal_no_format text
44
+ musl << text
45
+ end
46
+
47
+ def block start, finish, opts={}
48
+ musl << indent(depth) + start + "\r\n"
49
+ @depth += 1
50
+ yield self
51
+ @depth -= 1
52
+ musl << indent(depth) + finish + "\r\n"
53
+ end
54
+
55
+ def create name # yield |scenario|
56
+ block 'scenario(name: "' + name + '") {', '}' do
57
+ yield self
58
+ end
59
+ end
60
+
61
+ def hosts # yield |hosts|
62
+ block 'hosts {', '}' do
63
+ yield Hosts.new(self)
64
+ end
65
+ end
66
+
67
+ def options # yield |options|
68
+ block 'options {', '}' do
69
+ yield Options.new(self)
70
+ end
71
+ end
72
+
73
+ def globals # yield |globals|
74
+ block 'variables {', '}' do
75
+ yield Globals.new(self)
76
+ end
77
+ end
78
+
79
+ def steps # yield |steps|
80
+ block 'steps { ', '}' do
81
+ yield Steps.new(self)
82
+ end
83
+ end
84
+
85
+ def escape text
86
+ text.gsub(/\\/, '\\\\').gsub(/\r/, '\\r').gsub(/\n/, '\\n').gsub(/"/, '\\"')
87
+ end
88
+
89
+ def escape_payload text
90
+ text.gsub(/\\/, '\\\\\\').gsub(/\r/, '\\r').gsub(/\n/, '\\n').gsub(/"/, '\\"')
91
+ end
92
+
93
+ def attrs kvs = {}
94
+ return '' if kvs.empty?
95
+
96
+ ary = []
97
+ kvs.each_pair do |key, val|
98
+ if ( (val.is_a? String) && (val.length === 0) )
99
+ #val = escape(val.to_s)
100
+ val = '"' + escape(val.to_s) + '"'
101
+ elsif val.is_a? Symbol
102
+ val = escape(val.to_s)
103
+ else
104
+ val = escape(val.to_s)
105
+ end
106
+ ary << (key.to_s + ':' + val.to_s)
107
+ end
108
+ return '(' + ary.join(', ') + ')'
109
+ end
110
+ end
111
+
112
+ class Block
113
+ attr_reader :scenario
114
+
115
+ def initialize scenario
116
+ @scenario = scenario
117
+ end
118
+
119
+ def comment text
120
+ scenario.comment text
121
+ end
122
+ end
123
+
124
+ class Hosts < Block
125
+ def create name, type, role=nil
126
+ scenario.comment(role) if role
127
+ scenario.line('&' + name + ' = host(type:' + type + ')')
128
+ end
129
+ end
130
+
131
+ class Options < Block
132
+ def create name, value
133
+ v = value.is_a?(String) ? '"' + value + '"' : value
134
+ scenario.line('$' + name + ' = ' + v)
135
+ end
136
+ end
137
+
138
+ class Globals < Block
139
+ def create name, value
140
+ scenario.line('@' + name + ' = ' + value)
141
+ end
142
+ end
143
+
144
+ class Steps < Block
145
+ def xport name, klass, kvs
146
+ scenario.line(name + ' = ' + klass + scenario.attrs(kvs));
147
+ scenario.line;
148
+ end
149
+
150
+ def client_send name, xport, kvs={}
151
+ decl = name + ' = ' + xport + '.client_send' + scenario.attrs(kvs)
152
+ scenario.block decl + ' {', '}' do
153
+ yield Send.new(scenario)
154
+ end
155
+ end
156
+
157
+ def server_send name, xport, kvs={}
158
+ decl = name + ' = ' + xport + '.server_send' + scenario.attrs(kvs)
159
+ scenario.block decl + ' {', '}' do
160
+ yield Send.new(scenario)
161
+ end
162
+ end
163
+
164
+ def client_receive name, xport, kvs={}
165
+ decl = name + ' = ' + xport + '.client_receive' + scenario.attrs(kvs)
166
+ scenario.block decl + ' {', '}' do
167
+ yield Receive.new(scenario)
168
+ end
169
+ end
170
+
171
+ def server_receive name, xport, kvs={}
172
+ decl = name + ' = ' + xport + '.server_receive' + scenario.attrs(kvs)
173
+ scenario.block decl + ' {', '}' do
174
+ yield Receive.new(scenario)
175
+ end
176
+ end
177
+ end
178
+
179
+ class Send < Block
180
+ def base64_encode kvs={}
181
+ if block_given?
182
+ scenario.block 'base64_encode' + scenario.attrs(kvs) + ' [', ']' do
183
+ yield self
184
+ end
185
+ else
186
+ scenario.line 'base64_encode' + scenario.attrs(kvs)
187
+ end
188
+ end
189
+
190
+ def uri_percent_encode kvs={}
191
+ if block_given?
192
+ scenario.block 'uri_percent_encode' + scenario.attrs(kvs) + ' [', ']' do
193
+ yield self
194
+ end
195
+ else
196
+ scenario.line 'uri_percent_encode' + scenario.attrs(kvs)
197
+ end
198
+ end
199
+
200
+ def hex_encode kvs={}
201
+ if block_given?
202
+ scenario.block 'hex_encode' + scenario.attrs(kvs) + ' [', ']' do
203
+ yield self
204
+ end
205
+ else
206
+ scenario.line 'hex_encode' + scenario.attrs(kvs)
207
+ end
208
+ end
209
+
210
+ def block start, finish
211
+ scenario.block start, finish do
212
+ yield self
213
+ end
214
+ end
215
+
216
+ def compress type
217
+ scenario.block type + '_compress [', ']' do
218
+ yield self
219
+ end
220
+ end
221
+
222
+ def header name, kvs={}
223
+ kvs['header_name'] = name
224
+ if block_given?
225
+ scenario.block 'header(header_name:"' + name + '") [', ']' do
226
+ #scenario.block 'header' + scenario.attrs(kvs) + ' [', ']' do
227
+ yield self
228
+ end
229
+ else
230
+ scenario.line 'header' + scenario.attrs(kvs)
231
+ end
232
+ end
233
+
234
+ def length_string kvs={}
235
+ scenario.line 'length_string' + scenario.attrs(kvs)
236
+ end
237
+
238
+ def line text=''
239
+ if block_given?
240
+ scenario.block 'line [', ']' do
241
+ yield self
242
+ end
243
+ else
244
+ scenario.line('"' + scenario.escape(text) + '\r\n"')
245
+ end
246
+ end
247
+
248
+ def string text=''
249
+ scenario.line('"' + scenario.escape(text) + '"')
250
+ end
251
+
252
+ def literal text
253
+ scenario.literal text
254
+ end
255
+
256
+ def literal_no_format text
257
+ scenario.literal_no_format text
258
+ end
259
+ end
260
+
261
+ class Receive < Block
262
+ def assertions
263
+ scenario.block 'assertions {', '}' do
264
+ yield Assertions.new(scenario)
265
+ end
266
+ end
267
+
268
+ def variables
269
+ scenario.block 'variables {', '}' do
270
+ yield Variables.new(scenario)
271
+ end
272
+ end
273
+ end
274
+
275
+ class Assertions < Block
276
+ def create text
277
+ scenario.line(text)
278
+ end
279
+ end
280
+
281
+ class Variables < Block
282
+ def create text
283
+ scenario.line(text)
284
+ end
285
+ end
286
+
287
+ end
288
+ end
@@ -0,0 +1,559 @@
1
+ # Copyright (C) 2008 Mu Dynamics, Inc
2
+ #
3
+ # This program is confidential and proprietary to Mu Dynamics, Inc and
4
+ # may not be reproduced, published or disclosed to others without its
5
+ # authorization.
6
+
7
+ require 'libxml'
8
+ require 'mu/libxml'
9
+
10
+ # Helper mixin for XML serialization.
11
+ #
12
+ # Example:
13
+ # class MyObject
14
+ # include XMLizable
15
+ # xmlizable 'my-object' do
16
+ # xml_element :a, Integer
17
+ # xml_attribute :b, String, :required => true
18
+ # end
19
+ # ...
20
+ module XMLizable
21
+ class XMLParseError < StandardError ; end
22
+
23
+ def self.included klass
24
+ super
25
+ klass.extend XMLizableClassMethods
26
+ end
27
+
28
+ module XMLizableClassMethods
29
+ # Declare object to be XML serializable. If name is not given, the
30
+ # object itself cannot be serialized and only XMLizable subclasses can.
31
+ def xmlizable name=nil #block
32
+ if defined? @xmlizable_called
33
+ raise ArgumentError, "Only call xmlizable once"
34
+ end
35
+ @xmlizable_called = true
36
+ @xmlizable_name = name
37
+ @xmlizable_classes = {}
38
+ if name
39
+ # Add this class to ancestor's XMLizable class map.
40
+ classes = ancestors.select { |ancestor| ancestor < XMLizable }
41
+ classes.each do |ancestor|
42
+ if ancestor.instance_variable_defined? :@xmlizable_classes
43
+ classes =
44
+ ancestor.instance_variable_get :@xmlizable_classes
45
+ classes[name] = self
46
+ end
47
+ end
48
+ end
49
+
50
+ # Set xmlizable elements and attributes in block
51
+ @xmlizable_elements = []
52
+ @xmlizable_attributes = []
53
+ if block_given?
54
+ yield
55
+ end
56
+
57
+ # Re-init to merge in ancestor elements and attributes
58
+ init_elements_and_attrs
59
+ end
60
+
61
+ # Initialize @xmlizable_elements and @xmlizable_attributes
62
+ def init_elements_and_attrs
63
+ # Get non-duplicate elements/attibutes from ancestors (including
64
+ # current class)
65
+ elems = []
66
+ attrs = []
67
+ attr_names = Set.new
68
+ elem_names = Set.new
69
+ ancestors.each do |klass|
70
+ next unless klass < XMLizable
71
+ if klass.instance_variable_defined? :@xmlizable_elements
72
+ klass.xmlizable_elements.reverse_each do |e|
73
+ elems.unshift e unless elem_names.include? e.name
74
+ elem_names << e.name
75
+ end
76
+ end
77
+ if klass.instance_variable_defined? :@xmlizable_attributes
78
+ klass.xmlizable_attributes.reverse_each do |a|
79
+ attrs.unshift a unless attr_names.include? a.name
80
+ attr_names << a.name
81
+ end
82
+ end
83
+ end
84
+ @xmlizable_attributes = attrs.freeze
85
+ @xmlizable_elements = elems.freeze
86
+ end
87
+
88
+ # There may be subclasses that indirectly include XMLizable or that never
89
+ # call xmlizable. Use inherited hook to make sure they are initialized
90
+ def inherited(klass)
91
+ klass.init_elements_and_attrs
92
+ end
93
+
94
+ # Declare variable to be serialized as an XML element of type.
95
+ def xml_element name, klass, *opts
96
+ element = xml_object name, klass, *opts
97
+ @xmlizable_elements << element
98
+ end
99
+
100
+ # Declare variable to be serialized as an array of XML element with
101
+ # of type. The type must support to/from_libxml.
102
+ def xml_elements name, klass, *opts
103
+ element = xml_object name, klass, *opts
104
+ element.array = true
105
+ @xmlizable_elements << element
106
+ end
107
+
108
+ # Declare variable to be serialized as an XML attribute of type.
109
+ def xml_attribute name, klass, *opts
110
+ attribute = xml_object name, klass, *opts
111
+ @xmlizable_attributes << attribute
112
+ end
113
+
114
+ def xmlizable_name
115
+ @xmlizable_name
116
+ end
117
+
118
+ # Get list of elements.
119
+ def xmlizable_elements
120
+ @xmlizable_elements
121
+ end
122
+
123
+ # Get list of attributes.
124
+ def xmlizable_attributes
125
+ @xmlizable_attributes
126
+ end
127
+
128
+ # Create object to XML::Node. The object must be creatable by
129
+ # calling a constructor with no arguments.
130
+ def from_libxml node
131
+ if node.name != @xmlizable_name
132
+ xmlizable_classes = @xmlizable_classes
133
+ klass = xmlizable_classes[node.name]
134
+ if klass and klass != self
135
+ return klass.from_libxml(node)
136
+ else
137
+ raise XMLParseError, "Unknown element: #{node.name}"
138
+ end
139
+ end
140
+
141
+ object = self.new
142
+ @xmlizable_attributes.each do |attribute|
143
+ value = node[attribute.xml_name]
144
+ if value
145
+ attribute_klass = attribute.klass
146
+ if attribute_klass == String
147
+ # Common case with no special treatment
148
+ elsif attribute_klass <= Integer
149
+ value = value.to_i
150
+ elsif attribute_klass <= Float
151
+ value = value.to_f
152
+ elsif attribute_klass <= TrueClass
153
+ if value == 'true'
154
+ value = true
155
+ elsif value == 'false'
156
+ value = false
157
+ else
158
+ raise XMLParseError, 'Invalid boolean value: ' +
159
+ value.inspect
160
+ end
161
+ elsif attribute_klass <= Symbol
162
+ begin
163
+ value = value.to_sym
164
+ rescue ArgumentError
165
+ raise XMLParseError, "Invalid value: #{value.inspect} in #{node.name}"
166
+ end
167
+ if attribute.enum and not attribute.enum.member? value
168
+ raise XMLParseError, "Invalid value: #{value.inspect} in #{node.name}"
169
+ end
170
+ end
171
+ object.send :"#{attribute.name}=", value
172
+ elsif attribute.required
173
+ raise XMLParseError, "Required attribute missing: " +
174
+ attribute.xml_name + ' in ' + node.name
175
+ else
176
+ object.send "#{attribute.name}=", attribute.default
177
+ end
178
+ end
179
+ @xmlizable_elements.each do |element|
180
+ element_node = node.first_element element.xml_name
181
+ if element_node
182
+ element_klass = element.klass
183
+ if element_klass == String or element_klass == Mu::Scenario::Field::Reference
184
+ content = element_node.content
185
+ case element.escape
186
+ when :default
187
+ content.delete! TAB_CR_LF
188
+ value = XMLizable.unescape content
189
+ when :no_newline
190
+ content.delete! TAB_CR
191
+ value = XMLizable.unescape content
192
+ when :regexp
193
+ value = content
194
+ else
195
+ raise XMLParseError, "Unknown escape type: #{element.escape}"
196
+ end
197
+ elsif element_klass == Integer
198
+ value = element_node.content.to_i(0)
199
+ elsif element.array
200
+ value = []
201
+ element_node.each_element do |value_node|
202
+ value << element.klass.from_libxml(value_node)
203
+ end
204
+ elsif element_klass == Float
205
+ value = element_node.content.to_f
206
+ elsif element_klass == Symbol
207
+ value = element_node.content
208
+ begin
209
+ value = value.to_sym
210
+ rescue ArgumentError
211
+ raise XMLParseError, "Invalid value: #{value.inspect} in #{node.name}"
212
+ end
213
+ if element.enum and not element.enum.member? value
214
+ raise XMLParseError, "Invalid value: #{value.inspect} in #{node.name}"
215
+ end
216
+ elsif element_klass == TrueClass
217
+ value = element_node.content
218
+ if value == 'true'
219
+ value = true
220
+ elsif value == 'false'
221
+ value = false
222
+ else
223
+ raise XMLParseError, 'Invalid boolean value: ' +
224
+ value.inspect
225
+ end
226
+ else
227
+ value_node = element_node.first_element
228
+ if not value_node
229
+ raise XMLParseError, 'Element missing value: ' +
230
+ element.xml_name + ' in ' + node.name
231
+ end
232
+ value = element_klass.from_libxml value_node
233
+ end
234
+ object.send :"#{element.name}=", value
235
+ elsif element.required
236
+ raise XMLParseError, 'Required element missing: ' +
237
+ element.xml_name + ' in ' + node.name
238
+ else
239
+ object.send :"#{element.name}=", element.default
240
+ end
241
+ end
242
+ object.post_from_libxml node
243
+ return object
244
+ end
245
+
246
+ private
247
+
248
+ # Create new XML serializable object. Internal helper method.
249
+ XMLObject = ::Struct.new :name, :klass, :default, :required, :internal,
250
+ :array, :enum, :xml_name, :label, :doc, :escape
251
+ def xml_object name, klass, *opts
252
+ # Get default value
253
+ if not opts[0].is_a? Hash
254
+ default = opts.shift
255
+ else
256
+ default = nil
257
+ end
258
+ array = false
259
+ if opts[-1]
260
+ required = opts[-1].fetch :required, false
261
+ internal = opts[-1].fetch :internal, false
262
+ enum = opts[-1][:enum]
263
+ xml_name = opts[-1].fetch :xml_name, name.to_s
264
+ label = opts[-1][:label]
265
+ doc = opts[-1][:doc]
266
+ escape = opts[-1].fetch :escape, :default
267
+ else
268
+ required = false
269
+ internal = false
270
+ enum = nil
271
+ xml_name = name.to_s
272
+ label = nil
273
+ doc = nil
274
+ escape = :default
275
+ end
276
+ return XMLObject.new(name, klass, default, required, internal,
277
+ array, enum, xml_name, label, doc, escape)
278
+ end
279
+ end
280
+
281
+ # Traverses the XMLizable tree rooted at the current node, passing
282
+ # each child node to the associated block. If the block returns a
283
+ # value other than nil or false the child is replaced with that
284
+ # value.
285
+ def xmlizable_replace_nodes &block
286
+ self.class.xmlizable_attributes.each do |attribute|
287
+ obj = self.send attribute.name
288
+ if nobj = block.call(obj)
289
+ self.send :"#{attribute.name}=", nobj
290
+ end
291
+ end
292
+
293
+ self.class.xmlizable_elements.each do |element|
294
+ obj = self.send element.name
295
+ recurse = element.klass <= XMLizable
296
+ if element.array
297
+ obj.each_with_index do |sub_obj,i|
298
+ if nsub_obj = block.call(sub_obj)
299
+ obj[i] = nsub_obj
300
+ elsif sub_obj and recurse
301
+ sub_obj.xmlizable_replace_nodes(&block)
302
+ end
303
+ end
304
+ else
305
+ if nobj = block.call(obj)
306
+ self.send :"#{element.name}=", nobj
307
+ elsif obj and recurse
308
+ obj.xmlizable_replace_nodes(&block)
309
+ end
310
+ end
311
+ end
312
+ end
313
+
314
+ def xmlizable_map! &block
315
+ self.class.xmlizable_attributes.each do |attribute|
316
+ obj = self.send attribute.name
317
+ if nobj = block.call(obj) and not nobj.equal?(obj)
318
+ self.send :"#{attribute.name}=", nobj
319
+ end
320
+ end
321
+
322
+ self.class.xmlizable_elements.each do |element|
323
+ obj = self.send element.name
324
+ if element.array
325
+ obj.each_with_index do |sub_obj,i|
326
+ if nsub_obj = block.call(sub_obj) and not nsub_obj.equal?(sub_obj)
327
+ obj[i] = nsub_obj
328
+ end
329
+ end
330
+ else
331
+ if nobj = block.call(obj) and not nobj.equal?(obj)
332
+ self.send :"#{element.name}=", nobj
333
+ end
334
+ end
335
+ end
336
+ end
337
+
338
+ def xmlizable_each &block
339
+ self.class.xmlizable_attributes.each do |attribute|
340
+ obj = self.send attribute.name
341
+ block.call(obj)
342
+ end
343
+
344
+ self.class.xmlizable_elements.each do |element|
345
+ obj = self.send element.name
346
+ if element.array
347
+ obj.each do |sub_obj|
348
+ block.call(sub_obj)
349
+ end
350
+ else
351
+ block.call(obj)
352
+ end
353
+ end
354
+ end
355
+
356
+ # Recuses through xmlizable tree, yielding each element and attribute
357
+ # to the supplied block. You can stop recursion at a given node
358
+ # with "throw :prune" (like Find.find from stdlib)
359
+ def xmlizable_find &block
360
+ catch :prune do
361
+ block.call(self)
362
+
363
+ self.class.xmlizable_attributes.each do |attribute|
364
+ catch :prune do
365
+ obj = self.send attribute.name
366
+ block.call(obj)
367
+ end
368
+ end
369
+
370
+ self.class.xmlizable_elements.each do |element|
371
+ obj = self.send element.name
372
+ next if not obj
373
+
374
+ recurse = element.klass <= XMLizable
375
+ if element.array
376
+ obj.each do |sub_obj|
377
+ next unless sub_obj
378
+ if recurse
379
+ sub_obj.xmlizable_find(&block)
380
+ end
381
+ end
382
+ else
383
+ if recurse
384
+ obj.xmlizable_find(&block)
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end
390
+
391
+ # Same as xmlizable_find but only yields elements.
392
+ def xmlizable_find_elements &block
393
+ catch :prune do
394
+ block.call(self)
395
+
396
+ self.class.xmlizable_elements.each do |element|
397
+ obj = self.send element.name
398
+ next if not obj
399
+
400
+ recurse = element.klass <= XMLizable
401
+ if element.array
402
+ obj.each do |sub_obj|
403
+ next unless sub_obj
404
+ if recurse
405
+ sub_obj.xmlizable_find_elements(&block)
406
+ end
407
+ end
408
+ else
409
+ if recurse
410
+ obj.xmlizable_find_elements(&block)
411
+ end
412
+ end
413
+ end
414
+ end
415
+ end
416
+
417
+ # Convert object to XML::Node
418
+ def xmlizable_to_libxml
419
+ name = self.class.instance_variable_get :@xmlizable_name
420
+ if not name
421
+ raise ArgumentError, 'Cannot convert nameless class to XML: ' +
422
+ self.class.name
423
+ end
424
+ node = LibXML::XML::Node.new name
425
+ self.class.xmlizable_attributes.each do |attribute|
426
+ value = self.send attribute.name
427
+ if value == attribute.default and not attribute.required
428
+ next
429
+ end
430
+ node[attribute.xml_name] = value.to_s
431
+ end
432
+ self.class.xmlizable_elements.each do |element|
433
+ value = self.send element.name
434
+ element_klass = element.klass
435
+ if value == element.default and not element.required
436
+ next
437
+ end
438
+ element_node = LibXML::XML::Node.new element.xml_name
439
+ node << element_node
440
+ if element.array
441
+ value.each do |sub_value|
442
+ element_node << sub_value.to_libxml
443
+ end
444
+ elsif element_klass <= Integer or element_klass <= Float
445
+ element_node.content = value.to_s
446
+ elsif element_klass <= Symbol or element_klass <= TrueClass
447
+ element_node.content = value.to_s
448
+ elsif element_klass <= String
449
+ value = value.to_s.dup
450
+ case element.escape
451
+ when :default
452
+ element_node << XMLizable.escape(value, ESCAPES)
453
+ when :no_newline
454
+ element_node << XMLizable.escape(value, ESCAPES_NO_NEWLINE)
455
+ when :regexp
456
+ element_node << XMLizable.escape(value, ESCAPES_RE)
457
+ else
458
+ raise ArgumentError, "Unknown escape type: #{escape_table}"
459
+ end
460
+ else
461
+ element_node << value.to_libxml
462
+ end
463
+ end
464
+ return node
465
+ end
466
+
467
+ alias :to_libxml :xmlizable_to_libxml
468
+
469
+ # Hook for logic after de-serializing an object.
470
+ def post_from_libxml node
471
+ end
472
+
473
+
474
+ # Default map used for unescaping.
475
+ # Backslash escapes are not substituted unless they are defined here
476
+ # (or in a map explicitly passed to XMLizable.unescape). The exception
477
+ # to this is "\x" sequences like "\x0a" which are always substituted.
478
+ UNESCAPE_MAP = {
479
+ 'n' => "\n",
480
+ 't' => "\t",
481
+ 'r' => "\r",
482
+ '\\' => "\\"
483
+ }
484
+
485
+ # 1 2 3
486
+ RE_ESCAPE = /\A([^\\]+)?(?:\\(?:(?:x([0-9a-fA-F]{1,2}))|(.)))?/
487
+ # 1: optional text in front with no escapes
488
+ # then we parse optional escape sequence:
489
+ # 2: hex from \x escape
490
+ # 3: char from other escapes
491
+
492
+ # Returns an unescaped copy of string s.
493
+ def self.unescape s, unescape_map=nil
494
+ unescape_map ||= UNESCAPE_MAP
495
+
496
+ input = s.dup
497
+ output = []
498
+ until input.empty?
499
+ input.slice! RE_ESCAPE
500
+ # leading text with no escape sequences
501
+ output << $1 if $1
502
+ if $2
503
+ # We have an \x escape code. Interpret hex and get character.
504
+ output << $2.hex.chr
505
+ elsif $3
506
+ # We have something like \t. Interpet it if we can, otherwise preserve both characters
507
+ output << unescape_map.fetch($3,"\\#$3")
508
+ end
509
+ end
510
+ output.join
511
+ end
512
+
513
+ # Default table used for converting ascii code to escaped representation
514
+ ESCAPES = Array.new 256 do |i|
515
+ case i
516
+ when 9; "\\t".freeze
517
+ when 13; "\\r".freeze
518
+ when 92; "\\\\".freeze
519
+ when 10; "\\n".freeze
520
+ when 32..126; i.chr.freeze
521
+ else ; ('\x%02x' % i).freeze
522
+ end
523
+ end
524
+ ESCAPES.freeze
525
+ # Text escaped with ESCAPES should not have any of these characters in it.
526
+ TAB_CR_LF = "\t\r\n".freeze
527
+
528
+ ESCAPES_NONE = Array.new(256) { |i| i.chr.freeze }
529
+ ESCAPES_NONE.freeze
530
+
531
+ # Regular expressions are escaped when writing to xml but not when
532
+ # reading from XML. This is necessary because XML doesn't allow
533
+ # some (all?) binary characters. Escaping doesn't change the meaning
534
+ # of the regex and does not need to be reversed when deserealizing.
535
+ ESCAPES_RE = Array.new 256 do |i|
536
+ case i
537
+ when 32..126; i.chr.freeze
538
+ else ; ('\x%02x' % i).freeze
539
+ end
540
+ end
541
+ ESCAPES_RE.freeze
542
+
543
+ # Alternate escape table that doesn't escape newline characters
544
+ ESCAPES_NO_NEWLINE = ESCAPES.dup
545
+ ESCAPES_NO_NEWLINE[10] = "\n".freeze
546
+ ESCAPES_NO_NEWLINE.freeze
547
+ # Text escaped with ESCAPES_NO_NEWLINE should not have any of these characters in it.
548
+ TAB_CR = "\t\r".freeze
549
+
550
+ # Takes input and a table that maps ascii codes to their representation
551
+ def self.escape input, escapes=nil
552
+ escapes ||= ESCAPES
553
+ output = []
554
+ input.each_byte do |i|
555
+ output << escapes[i]
556
+ end
557
+ output.join
558
+ end
559
+ end