joe-merb-core 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +992 -0
  2. data/CONTRIBUTORS +94 -0
  3. data/LICENSE +20 -0
  4. data/PUBLIC_CHANGELOG +142 -0
  5. data/README +21 -0
  6. data/Rakefile +456 -0
  7. data/TODO +0 -0
  8. data/bin/merb +11 -0
  9. data/bin/merb-specs +5 -0
  10. data/lib/merb-core.rb +648 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +889 -0
  13. data/lib/merb-core/config.rb +380 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +620 -0
  16. data/lib/merb-core/controller/exceptions.rb +302 -0
  17. data/lib/merb-core/controller/merb_controller.rb +283 -0
  18. data/lib/merb-core/controller/mime.rb +111 -0
  19. data/lib/merb-core/controller/mixins/authentication.rb +123 -0
  20. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  21. data/lib/merb-core/controller/mixins/controller.rb +316 -0
  22. data/lib/merb-core/controller/mixins/render.rb +513 -0
  23. data/lib/merb-core/controller/mixins/responder.rb +469 -0
  24. data/lib/merb-core/controller/template.rb +254 -0
  25. data/lib/merb-core/core_ext.rb +9 -0
  26. data/lib/merb-core/core_ext/hash.rb +7 -0
  27. data/lib/merb-core/core_ext/kernel.rb +345 -0
  28. data/lib/merb-core/dispatch/cookies.rb +130 -0
  29. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  30. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +200 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +77 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +98 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +172 -0
  34. data/lib/merb-core/dispatch/request.rb +718 -0
  35. data/lib/merb-core/dispatch/router.rb +228 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +610 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +220 -0
  39. data/lib/merb-core/dispatch/router/route.rb +560 -0
  40. data/lib/merb-core/dispatch/session.rb +222 -0
  41. data/lib/merb-core/dispatch/session/container.rb +74 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +173 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +68 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +99 -0
  45. data/lib/merb-core/dispatch/session/store_container.rb +150 -0
  46. data/lib/merb-core/dispatch/worker.rb +28 -0
  47. data/lib/merb-core/gem_ext/erubis.rb +77 -0
  48. data/lib/merb-core/logger.rb +215 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +27 -0
  51. data/lib/merb-core/rack/adapter.rb +47 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +24 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +13 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +119 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +33 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +14 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +40 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +17 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +72 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +96 -0
  64. data/lib/merb-core/rack/middleware.rb +20 -0
  65. data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
  66. data/lib/merb-core/rack/middleware/content_length.rb +18 -0
  67. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  68. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  69. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  70. data/lib/merb-core/rack/middleware/static.rb +45 -0
  71. data/lib/merb-core/rack/middleware/tracer.rb +20 -0
  72. data/lib/merb-core/server.rb +321 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +252 -0
  75. data/lib/merb-core/tasks/merb.rb +2 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +51 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +17 -0
  79. data/lib/merb-core/test/helpers.rb +10 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +176 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +61 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +47 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +10 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +108 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +393 -0
  89. data/lib/merb-core/test/run_specs.rb +141 -0
  90. data/lib/merb-core/test/tasks/spectasks.rb +68 -0
  91. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  92. data/lib/merb-core/test/test_ext/object.rb +14 -0
  93. data/lib/merb-core/test/test_ext/string.rb +14 -0
  94. data/lib/merb-core/vendor/facets.rb +2 -0
  95. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  96. data/lib/merb-core/vendor/facets/inflect.rb +342 -0
  97. data/lib/merb-core/version.rb +3 -0
  98. metadata +253 -0
@@ -0,0 +1,31 @@
1
+ module Merb
2
+ autoload :AbstractController, "merb-core/controller/abstract_controller"
3
+ autoload :BootLoader, "merb-core/bootloader"
4
+ autoload :Config, "merb-core/config"
5
+ autoload :Const, "merb-core/constants"
6
+ autoload :ConditionalGetMixin, "merb-core/controller/mixins/conditional_get"
7
+ autoload :ControllerMixin, "merb-core/controller/mixins/controller"
8
+ autoload :ControllerExceptions, "merb-core/controller/exceptions"
9
+ autoload :Dispatcher, "merb-core/dispatch/dispatcher"
10
+ autoload :AuthenticationMixin, "merb-core/controller/mixins/authentication"
11
+ autoload :BasicAuthenticationMixin, "merb-core/controller/mixins/authentication/basic"
12
+ autoload :ErubisCaptureMixin, "merb-core/controller/mixins/erubis_capture"
13
+ autoload :Plugins, "merb-core/plugins"
14
+ autoload :Rack, "merb-core/rack"
15
+ autoload :RenderMixin, "merb-core/controller/mixins/render"
16
+ autoload :Request, "merb-core/dispatch/request"
17
+ autoload :ResponderMixin, "merb-core/controller/mixins/responder"
18
+ autoload :Router, "merb-core/dispatch/router"
19
+ autoload :Test, "merb-core/test"
20
+ autoload :Worker, "merb-core/dispatch/worker"
21
+ end
22
+
23
+ # Require this rather than autoloading it so we can be sure the default template
24
+ # gets registered
25
+ require 'merb-core/core_ext'
26
+ require "merb-core/controller/template"
27
+ require "merb-core/controller/merb_controller"
28
+
29
+ module Merb
30
+ module InlineTemplates; end
31
+ end
@@ -0,0 +1,889 @@
1
+ module Merb
2
+
3
+ class BootLoader
4
+
5
+ # def self.subclasses
6
+ #---
7
+ # @semipublic
8
+ cattr_accessor :subclasses, :after_load_callbacks, :before_load_callbacks, :finished
9
+ self.subclasses, self.after_load_callbacks,
10
+ self.before_load_callbacks, self.finished = [], [], [], []
11
+
12
+ class << self
13
+
14
+ # Adds the inheriting class to the list of subclasses in a position
15
+ # specified by the before and after methods.
16
+ #
17
+ # ==== Parameters
18
+ # klass<Class>:: The class inheriting from Merb::BootLoader.
19
+ def inherited(klass)
20
+ subclasses << klass.to_s
21
+ super
22
+ end
23
+
24
+ # ==== Parameters
25
+ # klass<~to_s>::
26
+ # The boot loader class after which this boot loader should be run.
27
+ #
28
+ #---
29
+ # @public
30
+ def after(klass)
31
+ move_klass(klass, 1)
32
+ end
33
+
34
+ # ==== Parameters
35
+ # klass<~to_s>::
36
+ # The boot loader class before which this boot loader should be run.
37
+ #
38
+ #---
39
+ # @public
40
+ def before(klass)
41
+ move_klass(klass, 0)
42
+ end
43
+
44
+ # Move a class that is inside the bootloader to some place in the Array,
45
+ # relative to another class.
46
+ #
47
+ # ==== Parameters
48
+ # klass<~to_s>::
49
+ # The klass to move the bootloader relative to
50
+ # where<Integer>::
51
+ # 0 means insert it before; 1 means insert it after
52
+ def move_klass(klass, where)
53
+ index = Merb::BootLoader.subclasses.index(klass.to_s)
54
+ if index
55
+ Merb::BootLoader.subclasses.delete(self.to_s)
56
+ Merb::BootLoader.subclasses.insert(index + where, self.to_s)
57
+ end
58
+ end
59
+
60
+ # Runs all boot loader classes by calling their run methods.
61
+ def run
62
+ Merb.started = true
63
+ subklasses = subclasses.dup
64
+ until subclasses.empty?
65
+ time = Time.now.to_i
66
+ bootloader = subclasses.shift
67
+ if (ENV['DEBUG'] || $DEBUG || Merb::Config[:verbose]) && Merb.logger
68
+ Merb.logger.debug!("Loading: #{bootloader}")
69
+ end
70
+ Object.full_const_get(bootloader).run
71
+ if (ENV['DEBUG'] || $DEBUG || Merb::Config[:verbose]) && Merb.logger
72
+ Merb.logger.debug!("It took: #{Time.now.to_i - time}")
73
+ end
74
+ self.finished << bootloader
75
+ end
76
+ self.subclasses = subklasses
77
+ end
78
+
79
+ # Determines whether or not a specific bootloader has finished yet.
80
+ #
81
+ # ==== Parameters
82
+ # bootloader<String, Class>:: The name of the bootloader to check.
83
+ #
84
+ # ==== Returns
85
+ # Boolean:: Whether or not the bootloader has finished.
86
+ def finished?(bootloader)
87
+ self.finished.include?(bootloader.to_s)
88
+ end
89
+
90
+ # Set up the default framework
91
+ #
92
+ # ==== Returns
93
+ # nil
94
+ #
95
+ #---
96
+ # @public
97
+ def default_framework
98
+ %w[view model helper controller mailer part].each do |component|
99
+ Merb.push_path(component.to_sym, Merb.root_path("app/#{component}s"))
100
+ end
101
+ Merb.push_path(:application, Merb.root_path("app" / "controllers" / "application.rb"))
102
+ Merb.push_path(:config, Merb.root_path("config"), nil)
103
+ Merb.push_path(:router, Merb.dir_for(:config), (Merb::Config[:router_file] || "router.rb"))
104
+ Merb.push_path(:lib, Merb.root_path("lib"), nil)
105
+ Merb.push_path(:log, Merb.log_path, nil)
106
+ Merb.push_path(:public, Merb.root_path("public"), nil)
107
+ Merb.push_path(:stylesheet, Merb.dir_for(:public) / "stylesheets", nil)
108
+ Merb.push_path(:javascript, Merb.dir_for(:public) / "javascripts", nil)
109
+ Merb.push_path(:image, Merb.dir_for(:public) / "images", nil)
110
+ nil
111
+ end
112
+
113
+ # ==== Parameters
114
+ # &block::
115
+ # A block to be added to the callbacks that will be executed after the
116
+ # app loads.
117
+ #
118
+ #---
119
+ # @public
120
+ def after_app_loads(&block)
121
+ after_load_callbacks << block
122
+ end
123
+
124
+ # ==== Parameters
125
+ # &block::
126
+ # A block to be added to the callbacks that will be executed before the
127
+ # app loads.
128
+ #
129
+ #---
130
+ # @public
131
+ def before_app_loads(&block)
132
+ before_load_callbacks << block
133
+ end
134
+ end
135
+
136
+ end
137
+
138
+ end
139
+
140
+ # Set up the logger.
141
+ #
142
+ # Place the logger inside of the Merb log directory (set up in
143
+ # Merb::BootLoader::BuildFramework)
144
+ class Merb::BootLoader::Logger < Merb::BootLoader
145
+
146
+ # Sets Merb.logger to a new logger created based on the config settings.
147
+ def self.run
148
+ Merb::Config[:log_level] ||= begin
149
+ if Merb.environment == "production"
150
+ Merb::Logger::Levels[:warn]
151
+ else
152
+ Merb::Logger::Levels[:debug]
153
+ end
154
+ end
155
+
156
+ Merb::Config[:log_stream] = Merb.log_stream
157
+
158
+ print_warnings
159
+ end
160
+
161
+ def self.print_warnings
162
+ if Gem::Version.new(Gem::RubyGemsVersion) < Gem::Version.new("1.1")
163
+ Merb.fatal! "Merb requires Rubygems 1.1 and later. " \
164
+ "Please upgrade RubyGems with gem update --system."
165
+ end
166
+ end
167
+ end
168
+
169
+ # Stores pid file.
170
+ #
171
+ # Only run if daemonization or clustering options specified on start.
172
+ # Port is taken from Merb::Config and must be already set at this point.
173
+ class Merb::BootLoader::DropPidFile < Merb::BootLoader
174
+ class << self
175
+
176
+ # Stores a PID file if Merb is running daemonized or clustered.
177
+ def run
178
+ Merb::Server.store_pid("main") #if Merb::Config[:daemonize] || Merb::Config[:cluster]
179
+ end
180
+ end
181
+ end
182
+
183
+ # Setup some useful defaults
184
+ class Merb::BootLoader::Defaults < Merb::BootLoader
185
+ def self.run
186
+ Merb::Request.http_method_overrides.concat([
187
+ proc { |c| c.params[:_method] },
188
+ proc { |c| c.env['HTTP_X_HTTP_METHOD_OVERRIDE'] }
189
+ ])
190
+ end
191
+ end
192
+
193
+
194
+ # Build the framework paths.
195
+ #
196
+ # By default, the following paths will be used:
197
+ # application:: Merb.root/app/controller/application.rb
198
+ # config:: Merb.root/config
199
+ # lib:: Merb.root/lib
200
+ # log:: Merb.root/log
201
+ # view:: Merb.root/app/views
202
+ # model:: Merb.root/app/models
203
+ # controller:: Merb.root/app/controllers
204
+ # helper:: Merb.root/app/helpers
205
+ # mailer:: Merb.root/app/mailers
206
+ # part:: Merb.root/app/parts
207
+ #
208
+ # To override the default, set Merb::Config[:framework] in your initialization
209
+ # file. Merb::Config[:framework] takes a Hash whose key is the name of the
210
+ # path, and whose values can be passed into Merb.push_path (see Merb.push_path
211
+ # for full details).
212
+ #
213
+ # ==== Notes
214
+ # All paths will default to Merb.root, so you can get a flat-file structure by
215
+ # doing Merb::Config[:framework] = {}.
216
+ #
217
+ # ==== Example
218
+ # Merb::Config[:framework] = {
219
+ # :view => Merb.root / "views",
220
+ # :model => Merb.root / "models",
221
+ # :lib => Merb.root / "lib",
222
+ # :public => [Merb.root / "public", nil]
223
+ # :router => [Merb.root / "config", "router.rb"]
224
+ # }
225
+ #
226
+ # That will set up a flat directory structure with the config files and
227
+ # controller files under Merb.root, but with models, views, and lib with their
228
+ # own folders off of Merb.root.
229
+ class Merb::BootLoader::BuildFramework < Merb::BootLoader
230
+ class << self
231
+
232
+ # Builds the framework directory structure.
233
+ def run
234
+ build_framework
235
+ end
236
+
237
+ # This method should be overridden in init.rb before Merb.start to set up
238
+ # a different framework structure.
239
+ def build_framework
240
+ if File.exists?(Merb.root / "config" / "framework.rb")
241
+ require Merb.root / "config" / "framework"
242
+ elsif File.exists?(Merb.root / "framework.rb")
243
+ require Merb.root / "framework"
244
+ else
245
+ Merb::BootLoader.default_framework
246
+ end
247
+ (Merb::Config[:framework] || {}).each do |name, path|
248
+ path = Array(path)
249
+ Merb.push_path(name, path.first, path.length == 2 ? path[1] : "**/*.rb")
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ class Merb::BootLoader::Dependencies < Merb::BootLoader
256
+
257
+ cattr_accessor :dependencies
258
+ self.dependencies = []
259
+
260
+ # Load the init_file specified in Merb::Config or if not specified, the
261
+ # init.rb file from the Merb configuration directory, and any environment
262
+ # files, which register the list of necessary dependencies and any
263
+ # after_app_loads hooks.
264
+ #
265
+ # Dependencies can hook into the bootloader process itself by using
266
+ # before or after insertion methods. Since these are loaded from this
267
+ # bootloader (Dependencies), they can only adapt the bootloaders that
268
+ # haven't been loaded up until this point.
269
+
270
+ def self.run
271
+ set_encoding
272
+ load_initfile
273
+ load_env_config
274
+ enable_json_gem unless Merb::disabled?(:json)
275
+ load_dependencies
276
+ update_logger
277
+ end
278
+
279
+ def self.load_dependencies
280
+ dependencies.each { |dependency| Kernel.load_dependency(dependency) }
281
+ end
282
+
283
+ def self.enable_json_gem
284
+ gem "json"
285
+ require "json/ext"
286
+ rescue LoadError
287
+ gem "json_pure"
288
+ require "json/pure"
289
+ end
290
+
291
+ def self.update_logger
292
+ # Clear out the logger so that any changes in init.rb will be picked up
293
+ Merb.logger = nil
294
+ end
295
+
296
+ def self.set_encoding
297
+ $KCODE = 'UTF8' if $KCODE == 'NONE' || $KCODE.blank?
298
+ end
299
+
300
+ private
301
+
302
+ # Determines the path for the environment configuration file
303
+ def self.env_config
304
+ Merb.dir_for(:config) / "environments" / (Merb.environment + ".rb")
305
+ end
306
+
307
+ # Checks to see whether or not an environment configuration exists
308
+ def self.env_config?
309
+ Merb.environment && File.exist?(env_config)
310
+ end
311
+
312
+ # Loads the environment configuration file, if any
313
+ def self.load_env_config
314
+ load(env_config) if env_config?
315
+ end
316
+
317
+ # Determines the init file to use, if any.
318
+ # By default Merb uses init.rb from application config directory.
319
+ def self.initfile
320
+ if Merb::Config[:init_file]
321
+ Merb::Config[:init_file].chomp(".rb") + ".rb"
322
+ else
323
+ Merb.dir_for(:config) / "init.rb"
324
+ end
325
+ end
326
+
327
+ # Loads the init file, should one exist
328
+ def self.load_initfile
329
+ load(initfile) if File.exists?(initfile)
330
+ end
331
+
332
+ end
333
+
334
+ class Merb::BootLoader::MixinSession < Merb::BootLoader
335
+
336
+ # Mixin the session functionality; this is done before BeforeAppLoads
337
+ # so that SessionContainer and SessionStoreContainer can be subclassed by
338
+ # plugin session stores for example - these need to be loaded in a
339
+ # before_app_loads block or a BootLoader that runs after MixinSession.
340
+ #
341
+ # Note: access to Merb::Config is needed, so it needs to run after
342
+ # Merb::BootLoader::Dependencies is done.
343
+ def self.run
344
+ require 'merb-core/dispatch/session'
345
+ Merb::Controller.send(:include, ::Merb::SessionMixin)
346
+ Merb::Request.send(:include, ::Merb::SessionMixin::RequestMixin)
347
+ end
348
+
349
+ end
350
+
351
+ class Merb::BootLoader::BeforeAppLoads < Merb::BootLoader
352
+
353
+ # Call any before_app_loads hooks that were registered via before_app_loads
354
+ # in any plugins.
355
+ def self.run
356
+ Merb::BootLoader.before_load_callbacks.each { |x| x.call }
357
+ end
358
+ end
359
+
360
+ # Load all classes inside the load paths.
361
+ #
362
+ # This is used in conjunction with Merb::BootLoader::ReloadClasses to track
363
+ # files that need to be reloaded, and which constants need to be removed in
364
+ # order to reload a file.
365
+ #
366
+ # This also adds the model, controller, and lib directories to the load path,
367
+ # so they can be required in order to avoid load-order issues.
368
+ class Merb::BootLoader::LoadClasses < Merb::BootLoader
369
+ LOADED_CLASSES = {}
370
+ MTIMES = {}
371
+
372
+ class << self
373
+
374
+ # Load all classes from Merb's native load paths.
375
+ def run
376
+ # Add models, controllers, helpers and lib to the load path
377
+ unless @ran
378
+ $LOAD_PATH.unshift Merb.dir_for(:model)
379
+ $LOAD_PATH.unshift Merb.dir_for(:controller)
380
+ $LOAD_PATH.unshift Merb.dir_for(:lib)
381
+ $LOAD_PATH.unshift Merb.dir_for(:helper)
382
+ end
383
+
384
+ @ran = true
385
+ $0 = "merb: master"
386
+
387
+ if Merb::Config[:fork_for_class_load] && Merb.env != "test"
388
+ start_transaction
389
+ else
390
+ trap('INT') do
391
+ Merb.logger.warn! "Killing children"
392
+ kill_children
393
+ end
394
+ end
395
+
396
+ # Load application file if it exists - for flat applications
397
+ load_file Merb.dir_for(:application) if File.file?(Merb.dir_for(:application))
398
+
399
+ # Load classes and their requirements
400
+ Merb.load_paths.each do |component, path|
401
+ next unless path.last && component != :application
402
+ load_classes(path.first / path.last)
403
+ end
404
+
405
+ Merb::Controller.send :include, Merb::GlobalHelpers
406
+ end
407
+
408
+ # Wait for any children to exit, remove the "main" PID, and
409
+ # exit.
410
+ def exit_gracefully
411
+ Process.waitall
412
+ Merb::Server.remove_pid("main")
413
+ exit
414
+ end
415
+
416
+ # If using fork-based code reloading, set up the BEGIN
417
+ # point and set up any signals in the parent and child.
418
+ def start_transaction
419
+ Merb.logger.warn! "Parent pid: #{Process.pid}"
420
+ reader, writer = nil, nil
421
+
422
+ if GC.respond_to?(:copy_on_write_friendly=)
423
+ GC.copy_on_write_friendly = true
424
+ end
425
+
426
+ loop do
427
+ reader, @writer = IO.pipe
428
+ pid = Kernel.fork
429
+
430
+ # pid means we're in the parent; only stay in the loop in that case
431
+ break unless pid
432
+ @writer.close
433
+
434
+ Merb::Server.store_pid("main")
435
+
436
+ if Merb::Config[:console_trap]
437
+ trap("INT") {}
438
+ else
439
+ trap("INT") do
440
+ Merb.logger.warn! "Killing children"
441
+ begin
442
+ Process.kill("ABRT", pid)
443
+ rescue SystemCallError
444
+ end
445
+ exit_gracefully
446
+ end
447
+ end
448
+
449
+ trap("HUP") do
450
+ Merb.logger.warn! "Doing a fast deploy\n"
451
+ Process.kill("HUP", pid)
452
+ end
453
+
454
+ reader_ary = [reader]
455
+ loop do
456
+ if exit_status = Process.wait2(pid, Process::WNOHANG)
457
+ exit_status[1] == 128 ? break : exit
458
+ end
459
+ if select(reader_ary, nil, nil, 0.25)
460
+ begin
461
+ next if reader.eof?
462
+ msg = reader.readline
463
+ if msg =~ /128/
464
+ break
465
+ else
466
+ exit_gracefully
467
+ end
468
+ rescue SystemCallError
469
+ exit_gracefully
470
+ end
471
+ end
472
+ end
473
+ end
474
+
475
+ reader.close
476
+
477
+ # add traps to the child
478
+ if Merb::Config[:console_trap]
479
+ Merb::Server.add_irb_trap
480
+ at_exit { kill_children }
481
+ else
482
+ trap('INT') {}
483
+ trap('ABRT') { kill_children }
484
+ trap('HUP') { kill_children(128) }
485
+ end
486
+ end
487
+
488
+ # Kill any children of the spawner process and exit with
489
+ # an appropriate status code.
490
+ #
491
+ # Note that exiting the spawner process with a status code
492
+ # of 128 when a master process exists will cause the
493
+ # spawner process to be recreated, and the app code reloaded.
494
+ #
495
+ # @param status<Integer> The status code to exit with
496
+ def kill_children(status = 0)
497
+ Merb.exiting = true unless status == 128
498
+
499
+ begin
500
+ @writer.puts(status.to_s) if @writer
501
+ rescue SystemCallError
502
+ end
503
+
504
+ threads = []
505
+
506
+ ($CHILDREN || []).each do |p|
507
+ threads << Thread.new do
508
+ begin
509
+ Process.kill("ABRT", p)
510
+ Process.wait2(p)
511
+ rescue SystemCallError
512
+ end
513
+ end
514
+ end
515
+ threads.each {|t| t.join }
516
+ exit(status)
517
+ end
518
+
519
+ # ==== Parameters
520
+ # file<String>:: The file to load.
521
+ def load_file(file)
522
+ # Don't do this expensive operation unless we need to
523
+ unless Merb::Config[:fork_for_class_load]
524
+ klasses = ObjectSpace.classes.dup
525
+ end
526
+
527
+ # Ignore the file for syntax errors. The next time
528
+ # the file is changed, it'll be reloaded again
529
+ begin
530
+ load file
531
+ rescue SyntaxError
532
+ return
533
+ ensure
534
+ if Merb::Config[:reload_classes]
535
+ MTIMES[file] = File.mtime(file)
536
+ end
537
+ end
538
+
539
+ # Don't do this expensive operation unless we need to
540
+ unless Merb::Config[:fork_for_class_load]
541
+ LOADED_CLASSES[file] = ObjectSpace.classes - klasses
542
+ end
543
+ end
544
+
545
+ # Load classes from given paths - using path/glob pattern.
546
+ #
547
+ # *paths<Array>::
548
+ # Array of paths to load classes from - may contain glob pattern
549
+ def load_classes(*paths)
550
+ orphaned_classes = []
551
+ paths.flatten.each do |path|
552
+ Dir[path].each do |file|
553
+ begin
554
+ load_file file
555
+ rescue NameError => ne
556
+ orphaned_classes.unshift(file)
557
+ end
558
+ end
559
+ end
560
+ load_classes_with_requirements(orphaned_classes)
561
+ end
562
+
563
+ # ==== Parameters
564
+ # file<String>:: The file to reload.
565
+ def reload(file)
566
+ if !Merb::Config[:fork_for_class_load]
567
+ remove_classes_in_file(file) { |f| load_file(f) }
568
+ else
569
+ kill_children(128)
570
+ end
571
+ end
572
+
573
+ # Reload the router to regenerate all routes.
574
+ def reload_router!
575
+ if File.file?(router_file = Merb.dir_for(:router) / Merb.glob_for(:router))
576
+ Merb::Router.reset!
577
+ reload router_file
578
+ end
579
+ end
580
+
581
+ # ==== Parameters
582
+ # file<String>:: The file to remove classes for.
583
+ # &block:: A block to call with the file that has been removed.
584
+ def remove_classes_in_file(file, &block)
585
+ Merb.klass_hashes.each {|x| x.protect_keys!}
586
+ if klasses = LOADED_CLASSES.delete(file)
587
+ klasses.each { |klass| remove_constant(klass) unless klass.to_s =~ /Router/ }
588
+ end
589
+ yield file if block_given?
590
+ Merb.klass_hashes.each {|x| x.unprotect_keys!}
591
+ end
592
+
593
+ # ==== Parameters
594
+ # const<Class>:: The class to remove.
595
+ def remove_constant(const)
596
+ # This is to support superclasses (like AbstractController) that track
597
+ # their subclasses in a class variable. Classes that wish to use this
598
+ # functionality are required to alias it to _subclasses_list. Plugins
599
+ # for ORMs and other libraries should keep this in mind.
600
+ superklass = const
601
+ until (superklass = superklass.superclass).nil?
602
+ if superklass.respond_to?(:_subclasses_list)
603
+ superklass.send(:_subclasses_list).delete(klass)
604
+ superklass.send(:_subclasses_list).delete(klass.to_s)
605
+ end
606
+ end
607
+
608
+ parts = const.to_s.split("::")
609
+ base = parts.size == 1 ? Object : Object.full_const_get(parts[0..-2].join("::"))
610
+ object = parts[-1].to_s
611
+ begin
612
+ base.send(:remove_const, object)
613
+ Merb.logger.debug("Removed constant #{object} from #{base}")
614
+ rescue NameError
615
+ Merb.logger.debug("Failed to remove constant #{object} from #{base}")
616
+ end
617
+ end
618
+
619
+ private
620
+
621
+ # "Better loading" of classes. If a class fails to load due to a NameError
622
+ # it will be added to the failed_classes and load cycle will be repeated unless
623
+ # no classes load.
624
+ #
625
+ # ==== Parameters
626
+ # klasses<Array[Class]>:: Classes to load.
627
+ def load_classes_with_requirements(klasses)
628
+ klasses.uniq!
629
+
630
+ while klasses.size > 0
631
+ # Note size to make sure things are loading
632
+ size_at_start = klasses.size
633
+
634
+ # List of failed classes
635
+ failed_classes = []
636
+ # Map classes to exceptions
637
+ error_map = {}
638
+
639
+ klasses.each do |klass|
640
+ klasses.delete(klass)
641
+ begin
642
+ load_file klass
643
+ rescue NameError => ne
644
+ error_map[klass] = ne
645
+ failed_classes.push(klass)
646
+ end
647
+ end
648
+
649
+ # Keep list of classes unique
650
+ failed_classes.each { |k| klasses.push(k) unless klasses.include?(k) }
651
+
652
+ # Stop processing if nothing loads or if everything has loaded
653
+ if klasses.size == size_at_start && klasses.size != 0
654
+ # Write all remaining failed classes and their exceptions to the log
655
+ messages = error_map.only(*failed_classes).map do |klass, e|
656
+ ["Could not load #{klass}:\n\n#{e.message} - (#{e.class})",
657
+ "#{(e.backtrace || []).join("\n")}"]
658
+ end
659
+ messages.each { |msg, trace| Merb.logger.fatal!("#{msg}\n\n#{trace}") }
660
+ Merb.fatal! "#{failed_classes.join(", ")} failed to load."
661
+ end
662
+ break if(klasses.size == size_at_start || klasses.size == 0)
663
+ end
664
+ end
665
+
666
+ end
667
+
668
+ end
669
+
670
+ class Merb::BootLoader::Templates < Merb::BootLoader
671
+ class << self
672
+
673
+ # Loads the templates into the Merb::InlineTemplates module.
674
+ def run
675
+ template_paths.each do |path|
676
+ Merb::Template.inline_template(File.open(path))
677
+ end
678
+ end
679
+
680
+ # ==== Returns
681
+ # Array[String]:: Template files found.
682
+ def template_paths
683
+ extension_glob = "{#{Merb::Template.template_extensions.join(',')}}"
684
+
685
+ # This gets all templates set in the controllers template roots
686
+ # We separate the two maps because most of controllers will have
687
+ # the same _template_root, so it's silly to be globbing the same
688
+ # path over and over.
689
+ controller_view_paths = []
690
+ Merb::AbstractController._abstract_subclasses.each do |klass|
691
+ next if (const = Object.full_const_get(klass))._template_root.blank?
692
+ controller_view_paths += const._template_roots.map { |pair| pair.first }
693
+ end
694
+ template_paths = controller_view_paths.uniq.compact.map { |path| Dir["#{path}/**/*.#{extension_glob}"] }
695
+
696
+ # This gets the templates that might be created outside controllers
697
+ # template roots. eg app/views/shared/*
698
+ template_paths << Dir["#{Merb.dir_for(:view)}/**/*.#{extension_glob}"] if Merb.dir_for(:view)
699
+
700
+ template_paths.flatten.compact.uniq
701
+ end
702
+ end
703
+ end
704
+
705
+ # Register the default MIME types:
706
+ #
707
+ # By default, the mime-types include:
708
+ # :all:: no transform, */*
709
+ # :yaml:: to_yaml, application/x-yaml or text/yaml
710
+ # :text:: to_text, text/plain
711
+ # :html:: to_html, text/html or application/xhtml+xml or application/html
712
+ # :xml:: to_xml, application/xml or text/xml or application/x-xml
713
+ # :js:: to_json, text/javascript ot application/javascript or application/x-javascript
714
+ # :json:: to_json, application/json or text/x-json
715
+ class Merb::BootLoader::MimeTypes < Merb::BootLoader
716
+
717
+ # Registers the default MIME types.
718
+ def self.run
719
+ Merb.add_mime_type(:all, nil, %w[*/*])
720
+ Merb.add_mime_type(:yaml, :to_yaml, %w[application/x-yaml text/yaml], :charset => "utf-8")
721
+ Merb.add_mime_type(:text, :to_text, %w[text/plain], :charset => "utf-8")
722
+ Merb.add_mime_type(:html, :to_html, %w[text/html application/xhtml+xml application/html], :charset => "utf-8")
723
+ Merb.add_mime_type(:xml, :to_xml, %w[application/xml text/xml application/x-xml], {:charset => "utf-8"}, 0.9998)
724
+ Merb.add_mime_type(:js, :to_json, %w[text/javascript application/javascript application/x-javascript], :charset => "utf-8")
725
+ Merb.add_mime_type(:json, :to_json, %w[application/json text/x-json], :charset => "utf-8")
726
+ end
727
+ end
728
+
729
+ class Merb::BootLoader::Cookies < Merb::BootLoader
730
+
731
+ def self.run
732
+ require 'merb-core/dispatch/cookies'
733
+ Merb::Controller.send(:include, Merb::CookiesMixin)
734
+ Merb::Request.send(:include, Merb::CookiesMixin::RequestMixin)
735
+ end
736
+
737
+ end
738
+
739
+ class Merb::BootLoader::SetupSession < Merb::BootLoader
740
+
741
+ # Enable the configured session container(s); any class that inherits from
742
+ # SessionContainer will be considered by its session_store_type attribute.
743
+ def self.run
744
+ # Require all standard session containers.
745
+ Dir[Merb.framework_root / "merb-core" / "dispatch" / "session" / "*.rb"].each do |file|
746
+ base_name = File.basename(file, ".rb")
747
+ require file unless base_name == "container" || base_name == "store_container"
748
+ end
749
+
750
+ # Set some defaults.
751
+ Merb::Config[:session_id_key] ||= "_session_id"
752
+
753
+ # List of all session_stores from :session_stores and :session_store config options.
754
+ config_stores = Merb::Config.session_stores
755
+
756
+ # Register all configured session stores - any loaded session container class
757
+ # (subclassed from Merb::SessionContainer) will be available for registration.
758
+ Merb::SessionContainer.subclasses.each do |class_name|
759
+ if(store = Object.full_const_get(class_name)) &&
760
+ config_stores.include?(store.session_store_type)
761
+ Merb::Request.register_session_type(store.session_store_type, class_name)
762
+ end
763
+ end
764
+
765
+ # Mixin the Merb::Session module to add app-level functionality to sessions
766
+ Merb::SessionContainer.send(:include, Merb::Session)
767
+ end
768
+
769
+ end
770
+
771
+ class Merb::BootLoader::AfterAppLoads < Merb::BootLoader
772
+
773
+ # Call any after_app_loads hooks that were registered via after_app_loads in
774
+ # init.rb.
775
+ def self.run
776
+ Merb::BootLoader.after_load_callbacks.each {|x| x.call }
777
+ end
778
+ end
779
+
780
+ # In case someone's running a sparse app, the default exceptions require the
781
+ # Exceptions class.
782
+ class Merb::BootLoader::SetupStubClasses < Merb::BootLoader
783
+ def self.run
784
+ unless defined?(Exceptions)
785
+ Object.class_eval <<-RUBY
786
+ class Application < Merb::Controller
787
+ abstract!
788
+ end
789
+
790
+ class Exceptions < Application
791
+ end
792
+ RUBY
793
+ end
794
+ end
795
+ end
796
+
797
+ class Merb::BootLoader::ChooseAdapter < Merb::BootLoader
798
+
799
+ # Choose the Rack adapter/server to use and set Merb.adapter.
800
+ def self.run
801
+ Merb.adapter = Merb::Rack::Adapter.get(Merb::Config[:adapter])
802
+ end
803
+ end
804
+
805
+ class Merb::BootLoader::StartWorkerThread < Merb::BootLoader
806
+
807
+ # Choose the Rack adapter/server to use and set Merb.adapter.
808
+ def self.run
809
+ Merb::Worker.new
810
+ end
811
+ end
812
+
813
+ class Merb::BootLoader::RackUpApplication < Merb::BootLoader
814
+ # Setup the Merb Rack App or read a rackup file located at
815
+ # Merb::Config[:rackup] with the same syntax as the
816
+ # rackup tool that comes with rack. Automatically evals the file in
817
+ # the context of a Rack::Builder.new { } block. Allows for mounting
818
+ # additional apps or middleware.
819
+ def self.run
820
+ require 'rack'
821
+ if File.exists?(Merb.dir_for(:config) / "rack.rb")
822
+ Merb::Config[:rackup] ||= Merb.dir_for(:config) / "rack.rb"
823
+ end
824
+
825
+ if Merb::Config[:rackup]
826
+ rackup_code = File.read(Merb::Config[:rackup])
827
+ Merb::Config[:app] = eval("::Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, Merb::Config[:rackup])
828
+ else
829
+ Merb::Config[:app] = ::Rack::Builder.new {
830
+ if prefix = ::Merb::Config[:path_prefix]
831
+ use Merb::Rack::PathPrefix, prefix
832
+ end
833
+ use Merb::Rack::Static, Merb.dir_for(:public)
834
+ run Merb::Rack::Application.new
835
+ }.to_app
836
+ end
837
+
838
+ end
839
+ end
840
+
841
+ class Merb::BootLoader::ReloadClasses < Merb::BootLoader
842
+
843
+ class TimedExecutor
844
+ def self.every(seconds, &block)
845
+ Thread.abort_on_exception = true
846
+ Thread.new do
847
+ loop do
848
+ sleep( seconds )
849
+ yield
850
+ end
851
+ Thread.exit
852
+ end
853
+ end
854
+ end
855
+
856
+ # Setup the class reloader if it's been specified in config.
857
+ def self.run
858
+ return unless Merb::Config[:reload_classes]
859
+
860
+ paths = []
861
+ Merb.load_paths.each do |path_name, file_info|
862
+ path, glob = file_info
863
+ next unless glob
864
+ paths << Dir[path / glob]
865
+ end
866
+
867
+ if Merb.dir_for(:application) && File.file?(Merb.dir_for(:application))
868
+ paths << Merb.dir_for(:application)
869
+ end
870
+
871
+ paths.flatten!
872
+
873
+ TimedExecutor.every(Merb::Config[:reload_time] || 0.5) do
874
+ GC.start
875
+ reload(paths)
876
+ end
877
+
878
+ end
879
+
880
+ # Reloads all files.
881
+ def self.reload(paths)
882
+ paths.each do |file|
883
+ next if LoadClasses::MTIMES[file] &&
884
+ LoadClasses::MTIMES[file] == File.mtime(file)
885
+
886
+ LoadClasses.reload(file)
887
+ end
888
+ end
889
+ end