merb-core 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. data/LICENSE +1 -1
  2. data/README +3 -3
  3. data/Rakefile +144 -33
  4. data/bin/merb +0 -0
  5. data/bin/merb-specs +0 -0
  6. data/docs/bootloading.dox +1 -0
  7. data/docs/merb-core-call-stack-diagram.mmap +0 -0
  8. data/docs/merb-core-call-stack-diagram.pdf +0 -0
  9. data/docs/merb-core-call-stack-diagram.png +0 -0
  10. data/lib/merb-core.rb +159 -37
  11. data/lib/merb-core/autoload.rb +1 -0
  12. data/lib/merb-core/bootloader.rb +208 -92
  13. data/lib/merb-core/config.rb +20 -6
  14. data/lib/merb-core/controller/abstract_controller.rb +113 -61
  15. data/lib/merb-core/controller/exceptions.rb +28 -13
  16. data/lib/merb-core/controller/merb_controller.rb +73 -44
  17. data/lib/merb-core/controller/mime.rb +25 -7
  18. data/lib/merb-core/controller/mixins/authentication.rb +1 -1
  19. data/lib/merb-core/controller/mixins/controller.rb +44 -8
  20. data/lib/merb-core/controller/mixins/render.rb +191 -128
  21. data/lib/merb-core/controller/mixins/responder.rb +65 -63
  22. data/lib/merb-core/controller/template.rb +103 -54
  23. data/lib/merb-core/core_ext.rb +7 -12
  24. data/lib/merb-core/core_ext/kernel.rb +128 -136
  25. data/lib/merb-core/dispatch/cookies.rb +26 -4
  26. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  27. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
  28. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  29. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +92 -0
  30. data/lib/merb-core/dispatch/dispatcher.rb +156 -224
  31. data/lib/merb-core/dispatch/request.rb +126 -25
  32. data/lib/merb-core/dispatch/router.rb +61 -6
  33. data/lib/merb-core/dispatch/router/behavior.rb +122 -41
  34. data/lib/merb-core/dispatch/router/route.rb +147 -22
  35. data/lib/merb-core/dispatch/session.rb +52 -2
  36. data/lib/merb-core/dispatch/session/cookie.rb +4 -2
  37. data/lib/merb-core/dispatch/session/memcached.rb +38 -27
  38. data/lib/merb-core/dispatch/session/memory.rb +18 -11
  39. data/lib/merb-core/dispatch/worker.rb +28 -0
  40. data/lib/merb-core/gem_ext/erubis.rb +58 -0
  41. data/lib/merb-core/logger.rb +3 -31
  42. data/lib/merb-core/plugins.rb +25 -3
  43. data/lib/merb-core/rack.rb +18 -12
  44. data/lib/merb-core/rack/adapter.rb +10 -8
  45. data/lib/merb-core/rack/adapter/ebb.rb +2 -2
  46. data/lib/merb-core/rack/adapter/irb.rb +31 -21
  47. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  48. data/lib/merb-core/rack/adapter/thin.rb +19 -9
  49. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  50. data/lib/merb-core/rack/application.rb +9 -84
  51. data/lib/merb-core/rack/middleware.rb +26 -0
  52. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  53. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  54. data/lib/merb-core/rack/middleware/static.rb +45 -0
  55. data/lib/merb-core/server.rb +27 -9
  56. data/lib/merb-core/tasks/audit.rake +68 -0
  57. data/lib/merb-core/tasks/merb.rb +1 -0
  58. data/lib/merb-core/tasks/merb_rake_helper.rb +12 -0
  59. data/lib/merb-core/tasks/stats.rake +71 -0
  60. data/lib/merb-core/test.rb +2 -1
  61. data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -3
  62. data/lib/merb-core/test/helpers/request_helper.rb +66 -24
  63. data/lib/merb-core/test/matchers/controller_matchers.rb +36 -4
  64. data/lib/merb-core/test/matchers/route_matchers.rb +12 -3
  65. data/lib/merb-core/test/matchers/view_matchers.rb +3 -3
  66. data/lib/merb-core/test/run_specs.rb +1 -0
  67. data/lib/merb-core/test/tasks/spectasks.rb +13 -5
  68. data/lib/merb-core/test/test_ext/string.rb +14 -0
  69. data/lib/merb-core/vendor/facets/dictionary.rb +3 -3
  70. data/lib/merb-core/vendor/facets/inflect.rb +82 -37
  71. data/lib/merb-core/version.rb +2 -2
  72. data/spec/private/config/config_spec.rb +39 -4
  73. data/spec/private/core_ext/kernel_spec.rb +3 -14
  74. data/spec/private/dispatch/bootloader_spec.rb +1 -1
  75. data/spec/private/dispatch/cookies_spec.rb +181 -69
  76. data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +0 -2
  77. data/spec/private/dispatch/fixture/app/controllers/foo.rb +0 -2
  78. data/spec/private/dispatch/fixture/config/rack.rb +10 -0
  79. data/spec/private/dispatch/fixture/log/merb_test.log +7054 -1802
  80. data/spec/private/dispatch/route_params_spec.rb +2 -3
  81. data/spec/private/dispatch/session_mixin_spec.rb +47 -0
  82. data/spec/private/plugins/plugin_spec.rb +73 -59
  83. data/spec/private/router/behavior_spec.rb +60 -0
  84. data/spec/private/router/fixture/log/merb_test.log +1693 -0
  85. data/spec/private/router/route_spec.rb +414 -0
  86. data/spec/private/router/router_spec.rb +175 -0
  87. data/spec/private/vendor/facets/plural_spec.rb +564 -0
  88. data/spec/private/vendor/facets/singular_spec.rb +489 -0
  89. data/spec/public/abstract_controller/controllers/cousins.rb +41 -0
  90. data/spec/public/abstract_controller/controllers/helpers.rb +12 -2
  91. data/spec/public/abstract_controller/controllers/partial.rb +17 -2
  92. data/spec/public/abstract_controller/controllers/render.rb +16 -1
  93. data/spec/public/abstract_controller/controllers/views/helpers/capture_eq/index.erb +1 -0
  94. data/spec/public/abstract_controller/controllers/views/helpers/capture_with_args/index.erb +1 -0
  95. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_two_throw_contents/index.erb +1 -0
  96. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/_collection.erb +1 -0
  97. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/index.erb +1 -0
  98. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/_partial.erb +1 -0
  99. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/index.erb +1 -0
  100. data/spec/public/abstract_controller/filter_spec.rb +20 -1
  101. data/spec/public/abstract_controller/helper_spec.rb +10 -2
  102. data/spec/public/abstract_controller/partial_spec.rb +8 -0
  103. data/spec/public/abstract_controller/render_spec.rb +8 -0
  104. data/spec/public/abstract_controller/spec_helper.rb +7 -3
  105. data/spec/public/boot_loader/boot_loader_spec.rb +2 -2
  106. data/spec/public/controller/base_spec.rb +10 -2
  107. data/spec/public/controller/config/init.rb +6 -0
  108. data/spec/public/controller/controllers/authentication.rb +9 -11
  109. data/spec/public/controller/controllers/base.rb +2 -8
  110. data/spec/public/controller/controllers/cookies.rb +16 -0
  111. data/spec/public/controller/controllers/dispatcher.rb +35 -0
  112. data/spec/public/controller/controllers/display.rb +62 -14
  113. data/spec/public/controller/controllers/redirect.rb +36 -0
  114. data/spec/public/controller/controllers/responder.rb +37 -11
  115. data/spec/public/controller/controllers/views/layout/custom_arg.json.erb +1 -0
  116. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.html.erb +1 -0
  117. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.xml.erb +1 -0
  118. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/no_layout.html.erb +1 -0
  119. data/spec/public/controller/cookies_spec.rb +23 -0
  120. data/spec/public/controller/dispatcher_spec.rb +411 -0
  121. data/spec/public/controller/display_spec.rb +43 -10
  122. data/spec/public/controller/redirect_spec.rb +33 -0
  123. data/spec/public/controller/responder_spec.rb +79 -11
  124. data/spec/public/controller/spec_helper.rb +3 -1
  125. data/spec/public/controller/url_spec.rb +10 -0
  126. data/spec/public/core/merb_core_spec.rb +11 -0
  127. data/spec/public/core_ext/fixtures/core_ext_dependency.rb +2 -0
  128. data/spec/public/core_ext/kernel_spec.rb +9 -0
  129. data/spec/public/core_ext/spec_helper.rb +1 -0
  130. data/spec/public/directory_structure/directory/log/merb_test.log +3729 -272
  131. data/spec/public/directory_structure/directory_spec.rb +3 -4
  132. data/spec/public/logger/logger_spec.rb +4 -4
  133. data/spec/public/reloading/directory/log/merb_test.log +288066 -15
  134. data/spec/public/reloading/reload_spec.rb +49 -27
  135. data/spec/public/request/multipart_spec.rb +26 -0
  136. data/spec/public/request/request_spec.rb +21 -2
  137. data/spec/public/router/fixation_spec.rb +27 -0
  138. data/spec/public/router/fixture/log/merb_test.log +30050 -0
  139. data/spec/public/router/nested_matches_spec.rb +97 -0
  140. data/spec/public/router/resource_spec.rb +1 -9
  141. data/spec/public/router/resources_spec.rb +0 -20
  142. data/spec/public/router/spec_helper.rb +27 -9
  143. data/spec/public/router/special_spec.rb +21 -8
  144. data/spec/public/template/template_spec.rb +17 -5
  145. data/spec/public/test/controller_matchers_spec.rb +10 -0
  146. data/spec/public/test/request_helper_spec.rb +29 -0
  147. data/spec/public/test/route_helper_spec.rb +18 -1
  148. data/spec/public/test/route_matchers_spec.rb +28 -1
  149. data/spec/public/test/view_matchers_spec.rb +3 -3
  150. data/spec/spec_helper.rb +56 -12
  151. metadata +89 -47
  152. data/lib/merb-core/core_ext/class.rb +0 -299
  153. data/lib/merb-core/core_ext/hash.rb +0 -426
  154. data/lib/merb-core/core_ext/mash.rb +0 -154
  155. data/lib/merb-core/core_ext/object.rb +0 -147
  156. data/lib/merb-core/core_ext/object_space.rb +0 -14
  157. data/lib/merb-core/core_ext/rubygems.rb +0 -28
  158. data/lib/merb-core/core_ext/set.rb +0 -46
  159. data/lib/merb-core/core_ext/string.rb +0 -89
  160. data/lib/merb-core/core_ext/time.rb +0 -13
  161. data/lib/merb-core/dispatch/exceptions.html.erb +0 -297
  162. data/spec/private/core_ext/class_spec.rb +0 -22
  163. data/spec/private/core_ext/hash_spec.rb +0 -522
  164. data/spec/private/core_ext/object_spec.rb +0 -121
  165. data/spec/private/core_ext/set_spec.rb +0 -26
  166. data/spec/private/core_ext/string_spec.rb +0 -167
  167. data/spec/private/core_ext/time_spec.rb +0 -16
  168. data/spec/private/dispatch/dispatch_spec.rb +0 -26
  169. data/spec/private/dispatch/fixture/log/development.log +0 -1
  170. data/spec/private/dispatch/fixture/log/merb.4000.pid +0 -1
  171. data/spec/private/dispatch/fixture/log/production.log +0 -1
  172. data/spec/private/dispatch/fixture/merb.4000.pid +0 -1
  173. data/spec/private/rack/application_spec.rb +0 -43
  174. data/spec/public/controller/log/merb.4000.pid +0 -1
  175. data/spec/public/directory_structure/directory/log/merb.4000.pid +0 -1
  176. data/spec/public/directory_structure/directory/merb.4000.pid +0 -1
  177. data/spec/public/reloading/directory/log/merb.4000.pid +0 -1
  178. data/spec/public/reloading/directory/merb.4000.pid +0 -1
@@ -1,299 +0,0 @@
1
- # Allows attributes to be shared within an inheritance hierarchy, but where
2
- # each descendant gets a copy of their parents' attributes, instead of just a
3
- # pointer to the same. This means that the child can add elements to, for
4
- # example, an array without those additions being shared with either their
5
- # parent, siblings, or children, which is unlike the regular class-level
6
- # attributes that are shared across the entire hierarchy.
7
- class Class
8
- # Defines class-level and instance-level attribute reader.
9
- #
10
- # ==== Parameters
11
- # *syms<Array>:: Array of attributes to define reader for.
12
- def cattr_reader(*syms)
13
- syms.flatten.each do |sym|
14
- next if sym.is_a?(Hash)
15
- class_eval(<<-EOS, __FILE__, __LINE__)
16
- unless defined? @@#{sym}
17
- @@#{sym} = nil
18
- end
19
-
20
- def self.#{sym}
21
- @@#{sym}
22
- end
23
-
24
- def #{sym}
25
- @@#{sym}
26
- end
27
- EOS
28
- end
29
- end
30
-
31
- # Defines class-level (and optionally instance-level) attribute writer.
32
- #
33
- # ==== Parameters
34
- # *syms<Array>:: Array of attributes to define writer for.
35
- #
36
- # ==== Options
37
- # :instance_writer<Boolean>:: if true, instance-level attribute writer is defined.
38
- def cattr_writer(*syms)
39
- options = syms.last.is_a?(Hash) ? syms.pop : {}
40
- syms.flatten.each do |sym|
41
- class_eval(<<-EOS, __FILE__, __LINE__)
42
- unless defined? @@#{sym}
43
- @@#{sym} = nil
44
- end
45
-
46
- def self.#{sym}=(obj)
47
- @@#{sym} = obj
48
- end
49
-
50
- #{"
51
-
52
- def #{sym}=(obj)
53
- @@#{sym} = obj
54
- end
55
- " unless options[:instance_writer] == false }
56
- EOS
57
- end
58
- end
59
-
60
- # Defines class-level (and optionally instance-level) attribute accessor.
61
- #
62
- # ==== Parameters
63
- # *syms<Array>:: Array of attributes to define accessor for.
64
- #
65
- # ==== Options
66
- # :instance_writer<Boolean>:: if true, instance-level attribute writer is defined.
67
- def cattr_accessor(*syms)
68
- cattr_reader(*syms)
69
- cattr_writer(*syms)
70
- end
71
-
72
- # Defines class-level inheritable attribute reader. Attributes are available to subclasses,
73
- # each subclass has a copy of parent's attribute.
74
- #
75
- # ==== Parameters
76
- # *syms<Array>:: Array of attributes to define inheritable reader for.
77
- def class_inheritable_reader(*syms)
78
- syms.each do |sym|
79
- next if sym.is_a?(Hash)
80
- class_eval <<-EOS, __FILE__, __LINE__
81
-
82
- def self.#{sym}
83
- read_inheritable_attribute(:#{sym})
84
- end
85
-
86
- def #{sym}
87
- self.class.#{sym}
88
- end
89
- EOS
90
- end
91
- end
92
-
93
- # Defines class-level inheritable attribute writer. Attributes are available to subclasses,
94
- # each subclass has a copy of parent's attribute.
95
- #
96
- # ==== Parameters
97
- # *syms<Array>:: Array of attributes to define inheritable writer for.
98
- #
99
- # ==== Options
100
- # :instance_writer<Boolean>:: if true, instance-level inheritable attribute writer is defined.
101
- def class_inheritable_writer(*syms)
102
- options = syms.last.is_a?(Hash) ? syms.pop : {}
103
- syms.each do |sym|
104
- class_eval <<-EOS, __FILE__, __LINE__
105
-
106
- def self.#{sym}=(obj)
107
- write_inheritable_attribute(:#{sym}, obj)
108
- end
109
-
110
- #{"
111
-
112
- def #{sym}=(obj)
113
- self.class.#{sym} = obj
114
- end
115
- " unless options[:instance_writer] == false }
116
- EOS
117
- end
118
- end
119
-
120
- # Defines class-level inheritable array writer. Arrays are available to subclasses,
121
- # each subclass has a copy of parent's array. Difference between other inheritable
122
- # attributes is that array is recreated every time it is written.
123
- #
124
- # ==== Parameters
125
- # *syms<Array>:: Array of array attribute names to define inheritable writer for.
126
- #
127
- # ==== Options
128
- # :instance_writer<Boolean>:: if true, instance-level inheritable array attribute writer is defined.
129
- def class_inheritable_array_writer(*syms)
130
- options = syms.last.is_a?(Hash) ? syms.pop : {}
131
- syms.each do |sym|
132
- class_eval <<-EOS, __FILE__, __LINE__
133
-
134
- def self.#{sym}=(obj)
135
- write_inheritable_array(:#{sym}, obj)
136
- end
137
-
138
- #{"
139
-
140
- def #{sym}=(obj)
141
- self.class.#{sym} = obj
142
- end
143
- " unless options[:instance_writer] == false }
144
- EOS
145
- end
146
- end
147
-
148
- # Defines class-level inheritable hash writer. Hashs are available to subclasses,
149
- # each subclass has a copy of parent's hash. Difference between other inheritable
150
- # attributes is that hash is recreated every time it is written.
151
- #
152
- # ==== Parameters
153
- # *syms<Array>:: Array of hash attribute names to define inheritable writer for.
154
- #
155
- # ==== Options
156
- # :instance_writer<Boolean>:: if true, instance-level inheritable hash attribute writer is defined.
157
- def class_inheritable_hash_writer(*syms)
158
- options = syms.last.is_a?(Hash) ? syms.pop : {}
159
- syms.each do |sym|
160
- class_eval <<-EOS, __FILE__, __LINE__
161
-
162
- def self.#{sym}=(obj)
163
- write_inheritable_hash(:#{sym}, obj)
164
- end
165
-
166
- #{"
167
-
168
- def #{sym}=(obj)
169
- self.class.#{sym} = obj
170
- end
171
- " unless options[:instance_writer] == false }
172
- EOS
173
- end
174
- end
175
-
176
- # Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
177
- # each subclass has a copy of parent's attribute.
178
- #
179
- # ==== Parameters
180
- # *syms<Array>:: Array of attributes to define inheritable accessor for.
181
- #
182
- # ==== Options
183
- # :instance_writer<Boolean>:: if true, instance-level inheritable attribute writer is defined.
184
- def class_inheritable_accessor(*syms)
185
- class_inheritable_reader(*syms)
186
- class_inheritable_writer(*syms)
187
- end
188
-
189
- # Defines class-level inheritable array accessor. Arrays are available to subclasses,
190
- # each subclass has a copy of parent's array. Difference between other inheritable
191
- # attributes is that array is recreated every time it is written.
192
- #
193
- # ==== Parameters
194
- # *syms<Array>:: Array of array attribute names to define inheritable accessor for.
195
- #
196
- # ==== Options
197
- # :instance_writer<Boolean>:: if true, instance-level inheritable array attribute writer is defined.
198
- def class_inheritable_array(*syms)
199
- class_inheritable_reader(*syms)
200
- class_inheritable_array_writer(*syms)
201
- end
202
-
203
- # Defines class-level inheritable hash accessor. Hashs are available to subclasses,
204
- # each subclass has a copy of parent's hash. Difference between other inheritable
205
- # attributes is that hash is recreated every time it is written.
206
- #
207
- # ==== Parameters
208
- # *syms<Array>:: Array of hash attribute names to define inheritable accessor for.
209
- #
210
- # ==== Options
211
- # :instance_writer<Boolean>:: if true, instance-level inheritable hash attribute writer is defined.
212
- def class_inheritable_hash(*syms)
213
- class_inheritable_reader(*syms)
214
- class_inheritable_hash_writer(*syms)
215
- end
216
-
217
- # ==== Returns
218
- # <Hash>:: inheritable attributes hash or it's default value, new frozen Hash.
219
- def inheritable_attributes
220
- @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
221
- end
222
-
223
- # Sets the attribute which copy is available to subclasses.
224
- #
225
- # ==== Parameters
226
- # key<~to_s, String, Symbol>:: inheritable attribute name
227
- # value<Anything but Array or Hash>:: value of inheritable attribute
228
- #
229
- # ==== Notes
230
- # If inheritable attributes storage has it's default value,
231
- # a new frozen hash, it is set to new Hash that is not frozen.
232
- def write_inheritable_attribute(key, value)
233
- if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
234
- @inheritable_attributes = {}
235
- end
236
- inheritable_attributes[key] = value
237
- end
238
-
239
- # Sets the array attribute which copy is available to subclasses.
240
- #
241
- # ==== Parameters
242
- # key<~to_s, String, Symbol>:: inheritable attribute name
243
- # value<Array>:: value of inheritable attribute
244
- #
245
- # ==== Notes
246
- # Inheritable array is re-created on each write.
247
- def write_inheritable_array(key, elements)
248
- write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
249
- write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
250
- end
251
-
252
- # Sets the hash attribute which copy is available to subclasses.
253
- #
254
- # ==== Parameters
255
- # key<~to_s, String, Symbol>:: inheritable attribute name
256
- # value<Hash>:: value of inheritable attribute
257
- #
258
- # ==== Notes
259
- # Inheritable hash is re-created on each write.
260
- def write_inheritable_hash(key, hash)
261
- write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
262
- write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
263
- end
264
-
265
- # Reads value of inheritable attributes.
266
- #
267
- # ==== Returns
268
- # Inheritable attribute value. Subclasses store copies of values.
269
- def read_inheritable_attribute(key)
270
- inheritable_attributes[key]
271
- end
272
-
273
- # Resets inheritable attributes to either EMPTY_INHERITABLE_ATTRIBUTES
274
- # if it is defined or it's default value, new frozen Hash.
275
- def reset_inheritable_attributes
276
- @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
277
- end
278
-
279
- private
280
- # Prevent this constant from being created multiple times
281
- EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
282
-
283
- def inherited_with_inheritable_attributes(child)
284
- inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
285
-
286
- if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
287
- new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
288
- else
289
- new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
290
- memo.update(key => (value.dup rescue value))
291
- end
292
- end
293
-
294
- child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
295
- end
296
-
297
- alias inherited_without_inheritable_attributes inherited
298
- alias inherited inherited_with_inheritable_attributes
299
- end
@@ -1,426 +0,0 @@
1
- require 'base64'
2
-
3
- class Hash
4
- class << self
5
- # Converts valid XML into a Ruby Hash structure.
6
- #
7
- # ==== Paramters
8
- # xml<String>:: A string representation of valid XML.
9
- #
10
- # ==== Notes
11
- # * Mixed content is treated as text and any tags in it are left unparsed
12
- # * Any attributes other than type on a node containing a text node will be
13
- # discarded
14
- #
15
- # ===== Typecasting
16
- # Typecasting is performed on elements that have a +type+ attribute:
17
- # integer::
18
- # boolean:: Anything other than "true" evaluates to false.
19
- # datetime::
20
- # Returns a Time object. See Time documentation for valid Time strings.
21
- # date::
22
- # Returns a Date object. See Date documentation for valid Date strings.
23
- #
24
- # Keys are automatically converted to +snake_case+
25
- #
26
- # ==== Examples
27
- #
28
- # ===== Standard
29
- # <user gender='m'>
30
- # <age type='integer'>35</age>
31
- # <name>Home Simpson</name>
32
- # <dob type='date'>1988-01-01</dob>
33
- # <joined-at type='datetime'>2000-04-28 23:01</joined-at>
34
- # <is-cool type='boolean'>true</is-cool>
35
- # </user>
36
- #
37
- # evaluates to
38
- #
39
- # { "user" => {
40
- # "gender" => "m",
41
- # "age" => 35,
42
- # "name" => "Home Simpson",
43
- # "dob" => DateObject( 1998-01-01 ),
44
- # "joined_at" => TimeObject( 2000-04-28 23:01),
45
- # "is_cool" => true
46
- # }
47
- # }
48
- #
49
- # ===== Mixed Content
50
- # <story>
51
- # A Quick <em>brown</em> Fox
52
- # </story>
53
- #
54
- # evaluates to
55
- #
56
- # { "story" => "A Quick <em>brown</em> Fox" }
57
- #
58
- # ====== Attributes other than type on a node containing text
59
- # <story is-good='false'>
60
- # A Quick <em>brown</em> Fox
61
- # </story>
62
- #
63
- # evaluates to
64
- #
65
- # { "story" => "A Quick <em>brown</em> Fox" }
66
- #
67
- # <bicep unit='inches' type='integer'>60</bicep>
68
- #
69
- # evaluates with a typecast to an integer. But unit attribute is ignored.
70
- #
71
- # { "bicep" => 60 }
72
- def from_xml( xml )
73
- ToHashParser.from_xml(xml)
74
- end
75
- end
76
-
77
- # ==== Returns
78
- # Mash:: This hash as a Mash for string or symbol key access.
79
- #
80
- # This class has semantics of ActiveSupport's HashWithIndifferentAccess
81
- # and we only have it so that people can write
82
- # params[:key] instead of params['key'].
83
- def to_mash
84
- hash = Mash.new(self)
85
- hash.default = default
86
- hash
87
- end
88
-
89
- # ==== Returns
90
- # String:: This hash as a query string
91
- #
92
- # ==== Examples
93
- # { :name => "Bob",
94
- # :address => {
95
- # :street => '111 Ruby Ave.',
96
- # :city => 'Ruby Central',
97
- # :phones => ['111-111-1111', '222-222-2222']
98
- # }
99
- # }.to_params
100
- # #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave."
101
- def to_params
102
- params = ''
103
- stack = []
104
-
105
- each do |k, v|
106
- if v.is_a?(Hash)
107
- stack << [k,v]
108
- else
109
- params << "#{k}=#{v}&"
110
- end
111
- end
112
-
113
- stack.each do |parent, hash|
114
- hash.each do |k, v|
115
- if v.is_a?(Hash)
116
- stack << ["#{parent}[#{k}]", v]
117
- else
118
- params << "#{parent}[#{k}]=#{v}&"
119
- end
120
- end
121
- end
122
-
123
- params.chop! # trailing &
124
- params
125
- end
126
-
127
- # ==== Parameters
128
- # *allowed:: The hash keys to include.
129
- #
130
- # ==== Returns
131
- # Hash:: A new hash with only the selected keys.
132
- #
133
- # ==== Examples
134
- # { :one => 1, :two => 2, :three => 3 }.only(:one)
135
- # #=> { :one => 1 }
136
- def only(*allowed)
137
- reject { |k,v| !allowed.include?(k) }
138
- end
139
-
140
- # ==== Parameters
141
- # *rejected:: The hash keys to exclude.
142
- #
143
- # ==== Returns
144
- # Hash:: A new hash without the selected keys.
145
- #
146
- # ==== Examples
147
- # { :one => 1, :two => 2, :three => 3 }.except(:one)
148
- # #=> { :two => 2, :three => 3 }
149
- def except(*rejected)
150
- reject { |k,v| rejected.include?(k) }
151
- end
152
-
153
- # ==== Returns
154
- # String:: The hash as attributes for an XML tag.
155
- #
156
- # ==== Examples
157
- # { :one => 1, "two"=>"TWO" }.to_xml_attributes
158
- # #=> 'one="1" two="TWO"'
159
- def to_xml_attributes
160
- map do |k,v|
161
- %{#{k.to_s.camel_case.sub(/^(.{1,1})/) { |m| m.downcase }}="#{v}"}
162
- end.join(' ')
163
- end
164
-
165
- alias_method :to_html_attributes, :to_xml_attributes
166
-
167
- # ==== Parameters
168
- # html_class<~to_s>::
169
- # The HTML class to add to the :class key. The html_class will be
170
- # concatenated to any existing classes.
171
- #
172
- # ==== Examples
173
- # hash[:class] #=> nil
174
- # hash.add_html_class!(:selected)
175
- # hash[:class] #=> "selected"
176
- # hash.add_html_class!("class1 class2")
177
- # hash[:class] #=> "selected class1 class2"
178
- def add_html_class!(html_class)
179
- if self[:class]
180
- self[:class] = "#{self[:class]} #{html_class}"
181
- else
182
- self[:class] = html_class.to_s
183
- end
184
- end
185
-
186
- # Converts all keys into string values. This is used during reloading to
187
- # prevent problems when classes are no longer declared.
188
- #
189
- # === Examples
190
- # hash = { One => 1, Two => 2 }.proctect_keys!
191
- # hash # => { "One" => 1, "Two" => 2 }
192
- def protect_keys!
193
- keys.each {|key| self[key.to_s] = delete(key) }
194
- end
195
-
196
- # Attempts to convert all string keys into Class keys. We run this after
197
- # reloading to convert protected hashes back into usable hashes.
198
- #
199
- # === Examples
200
- # # Provided that classes One and Two are declared in this scope:
201
- # hash = { "One" => 1, "Two" => 2 }.unproctect_keys!
202
- # hash # => { One => 1, Two => 2 }
203
- def unprotect_keys!
204
- keys.each do |key|
205
- (self[Object.full_const_get(key)] = delete(key)) rescue nil
206
- end
207
- end
208
-
209
- # Destructively and non-recursively convert each key to an uppercase string,
210
- # deleting nil values along the way.
211
- #
212
- # ==== Returns
213
- # Hash:: The newly environmentized hash.
214
- #
215
- # ==== Examples
216
- # { :name => "Bob", :contact => { :email => "bob@bob.com" } }.environmentize_keys!
217
- # #=> { "NAME" => "Bob", "CONTACT" => { :email => "bob@bob.com" } }
218
- def environmentize_keys!
219
- keys.each do |key|
220
- val = delete(key)
221
- next if val.nil?
222
- self[key.to_s.upcase] = val
223
- end
224
- self
225
- end
226
- end
227
-
228
- require 'rexml/parsers/streamparser'
229
- require 'rexml/parsers/baseparser'
230
- require 'rexml/light/node'
231
-
232
- # This is a slighly modified version of the XMLUtilityNode from
233
- # http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
234
- # It's mainly just adding vowels, as I ht cd wth n vwls :)
235
- # This represents the hard part of the work, all I did was change the
236
- # underlying parser.
237
- class REXMLUtilityNode # :nodoc:
238
- attr_accessor :name, :attributes, :children, :type
239
- cattr_accessor :typecasts, :available_typecasts
240
-
241
- self.typecasts = {}
242
- self.typecasts["integer"] = lambda{|v| v.nil? ? nil : v.to_i}
243
- self.typecasts["boolean"] = lambda{|v| v.nil? ? nil : (v.strip != "false")}
244
- self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
245
- self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)}
246
- self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
247
- self.typecasts["decimal"] = lambda{|v| BigDecimal(v)}
248
- self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f}
249
- self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f}
250
- self.typecasts["symbol"] = lambda{|v| v.to_sym}
251
- self.typecasts["string"] = lambda{|v| v.to_s}
252
- self.typecasts["yaml"] = lambda{|v| v.nil? ? nil : YAML.load(v)}
253
- self.typecasts["base64Binary"] = lambda{|v| Base64.decode64(v)}
254
-
255
- self.available_typecasts = self.typecasts.keys
256
-
257
- def initialize(name, attributes = {})
258
- @name = name.tr("-", "_")
259
- # leave the type alone if we don't know what it is
260
- @type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
261
-
262
- @nil_element = attributes.delete("nil") == "true"
263
- @attributes = undasherize_keys(attributes)
264
- @children = []
265
- @text = false
266
- end
267
-
268
- def add_node(node)
269
- @text = true if node.is_a? String
270
- @children << node
271
- end
272
-
273
- def to_hash
274
- if @type == "file"
275
- f = StringIO.new(::Base64.decode64(@children.first || ""))
276
- class << f
277
- attr_accessor :original_filename, :content_type
278
- end
279
- f.original_filename = attributes['name'] || 'untitled'
280
- f.content_type = attributes['content_type'] || 'application/octet-stream'
281
- return {name => f}
282
- end
283
-
284
- if @text
285
- return { name => typecast_value( translate_xml_entities( inner_html ) ) }
286
- else
287
- #change repeating groups into an array
288
- groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s }
289
-
290
- out = nil
291
- if @type == "array"
292
- out = []
293
- groups.each do |k, v|
294
- if v.size == 1
295
- out << v.first.to_hash.entries.first.last
296
- else
297
- out << v.map{|e| e.to_hash[k]}
298
- end
299
- end
300
- out = out.flatten
301
-
302
- else # If Hash
303
- out = {}
304
- groups.each do |k,v|
305
- if v.size == 1
306
- out.merge!(v.first)
307
- else
308
- out.merge!( k => v.map{|e| e.to_hash[k]})
309
- end
310
- end
311
- out.merge! attributes unless attributes.empty?
312
- out = out.empty? ? nil : out
313
- end
314
-
315
- if @type && out.nil?
316
- { name => typecast_value(out) }
317
- else
318
- { name => out }
319
- end
320
- end
321
- end
322
-
323
- # Typecasts a value based upon its type. For instance, if
324
- # +node+ has #type == "integer",
325
- # {{[node.typecast_value("12") #=> 12]}}
326
- #
327
- # ==== Parameters
328
- # value<String>:: The value that is being typecast.
329
- #
330
- # ==== :type options
331
- # "integer"::
332
- # converts +value+ to an integer with #to_i
333
- # "boolean"::
334
- # checks whether +value+, after removing spaces, is the literal
335
- # "true"
336
- # "datetime"::
337
- # Parses +value+ using Time.parse, and returns a UTC Time
338
- # "date"::
339
- # Parses +value+ using Date.parse
340
- #
341
- # ==== Returns
342
- # Integer, true, false, Time, Date, Object::
343
- # The result of typecasting +value+.
344
- #
345
- # ==== Notes
346
- # If +self+ does not have a "type" key, or if it's not one of the
347
- # options specified above, the raw +value+ will be returned.
348
- def typecast_value(value)
349
- return value unless @type
350
- proc = self.class.typecasts[@type]
351
- proc.nil? ? value : proc.call(value)
352
- end
353
-
354
- # Convert basic XML entities into their literal values.
355
- #
356
- # ==== Parameters
357
- # value<~gsub>::
358
- # An XML fragment.
359
- #
360
- # ==== Returns
361
- # ~gsub::
362
- # The XML fragment after converting entities.
363
- def translate_xml_entities(value)
364
- value.gsub(/&lt;/, "<").
365
- gsub(/&gt;/, ">").
366
- gsub(/&quot;/, '"').
367
- gsub(/&apos;/, "'").
368
- gsub(/&amp;/, "&")
369
- end
370
-
371
- # Take keys of the form foo-bar and convert them to foo_bar
372
- def undasherize_keys(params)
373
- params.keys.each do |key, value|
374
- params[key.tr("-", "_")] = params.delete(key)
375
- end
376
- params
377
- end
378
-
379
- # Get the inner_html of the REXML node.
380
- def inner_html
381
- @children.join
382
- end
383
-
384
- # Converts the node into a readable HTML node.
385
- #
386
- # ==== Returns
387
- # String:: The HTML node in text form.
388
- def to_html
389
- attributes.merge!(:type => @type ) if @type
390
- "<#{name}#{attributes.to_xml_attributes}>#{@nil_element ? '' : inner_html}</#{name}>"
391
- end
392
-
393
- # ==== Alias
394
- # #to_html
395
- def to_s
396
- to_html
397
- end
398
- end
399
-
400
- class ToHashParser # :nodoc:
401
-
402
- def self.from_xml(xml)
403
- stack = []
404
- parser = REXML::Parsers::BaseParser.new(xml)
405
-
406
- while true
407
- event = parser.pull
408
- case event[0]
409
- when :end_document
410
- break
411
- when :end_doctype, :start_doctype
412
- # do nothing
413
- when :start_element
414
- stack.push REXMLUtilityNode.new(event[1], event[2])
415
- when :end_element
416
- if stack.size > 1
417
- temp = stack.pop
418
- stack.last.add_node(temp)
419
- end
420
- when :text, :cdata
421
- stack.last.add_node(event[1]) unless event[1].strip.length == 0
422
- end
423
- end
424
- stack.pop.to_hash
425
- end
426
- end