mu 5.7.8 → 5.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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