module Merb class BootLoader # def self.subclasses # # :api: plugin cattr_accessor :subclasses, :after_load_callbacks, :before_load_callbacks, :finished, :before_worker_shutdown_callbacks, :before_master_shutdown_callbacks self.subclasses, self.after_load_callbacks, self.before_load_callbacks, self.finished, self.before_master_shutdown_callbacks, self.before_worker_shutdown_callbacks = [], [], [], [], [], [] class << self # Adds the inheriting class to the list of subclasses in a position # specified by the before and after methods. # # ==== Parameters # klass:: The class inheriting from Merb::BootLoader. # # ==== Returns # nil # # :api: plugin def inherited(klass) subclasses << klass.to_s super end # Execute this boot loader after the specified boot loader. # # ==== Parameters # klass<~to_s>:: # The boot loader class after which this boot loader should be run. # # ==== Returns # nil # # :api: plugin def after(klass) move_klass(klass, 1) nil end # Execute this boot loader before the specified boot loader. # # ==== Parameters # klass<~to_s>:: # The boot loader class before which this boot loader should be run. # # ==== Returns # nil # # :api: plugin def before(klass) move_klass(klass, 0) nil end # Move a class that is inside the bootloader to some place in the Array, # relative to another class. # # ==== Parameters # klass<~to_s>:: # The klass to move the bootloader relative to # where:: # 0 means insert it before; 1 means insert it after # # ==== Returns # nil # # :api: private def move_klass(klass, where) index = Merb::BootLoader.subclasses.index(klass.to_s) if index Merb::BootLoader.subclasses.delete(self.to_s) Merb::BootLoader.subclasses.insert(index + where, self.to_s) end nil end # Runs all boot loader classes by calling their run methods. # # ==== Returns # nil # # :api: plugin def run Merb.started = true subklasses = subclasses.dup until subclasses.empty? time = Time.now.to_i bootloader = subclasses.shift Merb.logger.debug!("Loading: #{bootloader}") if Merb.verbose_logging? Object.full_const_get(bootloader).run Merb.logger.debug!("It took: #{Time.now.to_i - time}") if Merb.verbose_logging? self.finished << bootloader end self.subclasses = subklasses nil end # Determines whether or not a specific bootloader has finished yet. # # ==== Parameters # bootloader:: The name of the bootloader to check. # # ==== Returns # Boolean:: Whether or not the bootloader has finished. # # :api: private def finished?(bootloader) self.finished.include?(bootloader.to_s) end # Set up the default framework # # ==== Returns # nil # # :api: plugin # @overridable def default_framework %w[view model helper controller mailer part].each do |component| Merb.push_path(component.to_sym, Merb.root_path("app/#{component}s")) end Merb.push_path(:application, Merb.root_path("app" / "controllers" / "application.rb")) Merb.push_path(:config, Merb.root_path("config"), nil) Merb.push_path(:router, Merb.dir_for(:config), (Merb::Config[:router_file] || "router.rb")) Merb.push_path(:lib, Merb.root_path("lib"), nil) Merb.push_path(:merb_session, Merb.root_path("merb" / "session")) Merb.push_path(:log, Merb.log_path, nil) Merb.push_path(:public, Merb.root_path("public"), nil) Merb.push_path(:stylesheet, Merb.dir_for(:public) / "stylesheets", nil) Merb.push_path(:javascript, Merb.dir_for(:public) / "javascripts", nil) Merb.push_path(:image, Merb.dir_for(:public) / "images", nil) nil end # Execute a block of code after the app loads. # # ==== Parameters # &block:: # A block to be added to the callbacks that will be executed after the # app loads. # # :api: public def after_app_loads(&block) after_load_callbacks << block end # Execute a block of code before the app loads but after dependencies load. # # ==== Parameters # &block:: # A block to be added to the callbacks that will be executed before the # app loads. # # :api: public def before_app_loads(&block) before_load_callbacks << block end # Execute a block of code before master process is shut down. # Only makes sense on platforms where Merb server can use forking. # # ==== Parameters # &block:: # A block to be added to the callbacks that will be executed # before master process is shut down. # # :api: public def before_master_shutdown(&block) before_master_shutdown_callbacks << block end # Execute a block of code before worker process is shut down. # Only makes sense on platforms where Merb server can use forking. # # ==== Parameters # &block:: # A block to be added to the callbacks that will be executed # before worker process is shut down. # # :api: public def before_worker_shutdown(&block) before_worker_shutdown_callbacks << block end end end end # Set up the logger. # # Place the logger inside of the Merb log directory (set up in # Merb::BootLoader::BuildFramework) class Merb::BootLoader::Logger < Merb::BootLoader # Sets Merb.logger to a new logger created based on the config settings. # # ==== Returns # nil # # :api: plugin def self.run Merb::Config[:log_level] ||= begin if Merb.environment == "production" Merb::Logger::Levels[:warn] else Merb::Logger::Levels[:debug] end end Merb::Config[:log_stream] = Merb::Config[:original_log_stream] || Merb.log_stream nil end end # Stores pid file. # # Only run if daemonization or clustering options specified on start. # Port is taken from Merb::Config and must be already set at this point. class Merb::BootLoader::DropPidFile < Merb::BootLoader class << self # Stores a PID file if Merb is running daemonized or clustered. # # ==== Returns # nil # # :api: plugin def run Merb::Server.store_pid("main") if Merb::Config[:daemonize] || Merb::Config[:cluster] nil end end end # Setup some useful defaults class Merb::BootLoader::Defaults < Merb::BootLoader # Sets up the defaults # # ==== Returns # nil # # :api: plugin def self.run Merb::Request.http_method_overrides.concat([ proc { |c| c.params[:_method] }, proc { |c| c.env['HTTP_X_HTTP_METHOD_OVERRIDE'] } ]) nil end end # Build the framework paths. # # By default, the following paths will be used: # application:: Merb.root/app/controller/application.rb # config:: Merb.root/config # lib:: Merb.root/lib # log:: Merb.root/log # view:: Merb.root/app/views # model:: Merb.root/app/models # controller:: Merb.root/app/controllers # helper:: Merb.root/app/helpers # mailer:: Merb.root/app/mailers # part:: Merb.root/app/parts # # To override the default, set Merb::Config[:framework] in your initialization # file. Merb::Config[:framework] takes a Hash whose key is the name of the # path, and whose values can be passed into Merb.push_path (see Merb.push_path # for full details). # # ==== Notes # All paths will default to Merb.root, so you can get a flat-file structure by # doing Merb::Config[:framework] = {}. # # ==== Example # Merb::Config[:framework] = { # :view => Merb.root / "views", # :model => Merb.root / "models", # :lib => Merb.root / "lib", # :public => [Merb.root / "public", nil] # :router => [Merb.root / "config", "router.rb"] # } # # That will set up a flat directory structure with the config files and # controller files under Merb.root, but with models, views, and lib with their # own folders off of Merb.root. class Merb::BootLoader::BuildFramework < Merb::BootLoader class << self # Builds the framework directory structure. # # ==== Returns # nil # # :api: plugin def run $:.push Merb.root unless Merb.root == File.expand_path(Dir.pwd) STDOUT.puts "Merb root at: #{Merb.root}" unless Merb.testing? build_framework nil end # Sets up merb paths to support the app's file layout. First, config/framework.rb is checked, # next we look for Merb.root/framework.rb, finally we use the default merb layout (Merb::BootLoader.default_framework) # # This method can be overriden to support other application layouts. # # ==== Returns # nil # # :api: plugin # @overridable def build_framework if File.exists?(Merb.root / "config" / "framework.rb") require Merb.root / "config" / "framework" elsif File.exists?(Merb.root / "framework.rb") require Merb.root / "framework" else Merb::BootLoader.default_framework end (Merb::Config[:framework] || {}).each do |name, path| path = Array(path) Merb.push_path(name, path.first, path.length == 2 ? path[1] : "**/*.rb") end nil end end end class Merb::BootLoader::Dependencies < Merb::BootLoader # ==== Returns # Array[Gem::Dependency]:: The dependencies registered in init.rb. # # As of Merb.1.1 these dependencies only get loaded when # Merb::Config[:kernel_dependencies] is set to true. # # This will be removed as of Merb 2.0 # # :api: plugin # @deprecated cattr_accessor :dependencies self.dependencies = [] # Load the init_file specified in Merb::Config or if not specified, the # init.rb file from the Merb configuration directory, and any environment # files and any after_app_loads hooks. # # Dependencies are loaded via Bunlder and managed in the Gemfile manifest. # By default manifest for Bundler is in the root directory of the app and # is called Gemfile. All dependencies MUST be definied there because all # dependency hangling was removed from Merb. # # ==== Deprecated (1.0.x) # Dependencies can hook into the bootloader process itself by using # before or after insertion methods. Since these are loaded from this # bootloader (Dependencies), they can only adapt the bootloaders that # haven't been loaded up until this point. # # ==== Returns # nil # # :api: plugin def self.run set_encoding load_dependencies unless Merb::disabled?(:initfile) load_initfile load_env_config end expand_ruby_path enable_json_gem unless Merb::disabled?(:json) update_logger nil end # Try to load the gem environment file (set via Merb::Config[:gemenv]) # defaults to ./gems/environment # # Load each the dependencies defined in the Merb::Config[:gemfile] # using the bundler gem's Bundler::require_env # # Falls back to rubygems if no bundler environment exists # # ==== Returns # nil # # :api: private def self.load_dependencies begin Bundler.require(:default, Merb.environment.to_sym) rescue Bundler::GemfileNotFound Merb.logger.error! "No Gemfile found! If you're generating new app with merb-gen " \ "this is fine, otherwise run: bundle init to create Gemfile" end nil end # Requires json or json_pure. # # ==== Returns # nil # # :api: private def self.enable_json_gem require "json" rescue LoadError Merb.logger.error! "You have enabled JSON but don't have json " \ "installed or don't have dependency in the Gemfile. " \ "Add \"gem 'json', '>= 1.1.7'\" or " \ "\"gem 'json_pure', '>= 1.1.7'\" to your Gemfile." end # Resets the logger and sets the log_stream to Merb::Config[:log_file] # if one is specified, falling back to STDOUT. # # ==== Returns # nil # # :api: private def self.update_logger Merb.reset_logger! # If log file is given, use it and not log stream we have. if Merb::Config[:log_file] log_file = Merb::Config[:log_file] raise "log file should be a string, got: #{log_file.inspect}" unless log_file.is_a?(String) STDOUT.puts "Logging to file at #{log_file}" unless Merb.testing? # try to create log directory (if it doesnt exist) log_directory = File.dirname(log_file) FileUtils.mkdir_p(log_directory) unless File.exists?(log_directory) Merb::Config[:log_stream] = File.open(log_file, "a") # but if it's not given, fallback to log stream or stdout else Merb::Config[:log_stream] ||= STDOUT end nil end # Default encoding to UTF8 if it has not already been set to something else. # # ==== Returns # nil # # :api: private def self.set_encoding unless RUBY_VERSION >= '1.9' $KCODE = 'UTF8' if $KCODE == 'NONE' || $KCODE.blank? end nil end private # Determines the path for the environment configuration file # # ==== Returns # String:: The path to the config file for the environment # # :api: private def self.env_config Merb.dir_for(:config) / "environments" / (Merb.environment + ".rb") end # Checks to see whether or not an environment configuration exists # # ==== Returns # Boolean:: Whether or not the environment configuration file exists. # # :api: private def self.env_config? Merb.environment && File.exist?(env_config) end # Loads the environment configuration file, if it is present # # ==== Returns # nil # # :api: private def self.load_env_config if env_config? env_config_path = relative_to_merb_path(env_config) STDOUT.puts "Loading #{env_config_path}" unless Merb.testing? load(env_config) end nil end # Determines the init file to use, if any. # By default Merb uses init.rb from application config directory. # # ==== Returns # nil # # :api: private def self.initfile if Merb::Config[:init_file] Merb::Config[:init_file].chomp(".rb") + ".rb" else Merb.dir_for(:config) / "init.rb" end end # Loads the init file, should one exist # # ==== Returns # nil # # :api: private def self.load_initfile return nil if Merb.const_defined?("INIT_RB_LOADED") if File.exists?(initfile) initfile_path = relative_to_merb_path(initfile) STDOUT.puts "Loading init file from #{initfile_path}" unless Merb.testing? load(initfile) Merb.const_set("INIT_RB_LOADED", true) elsif !Merb.testing? Merb.fatal! "You are not in a Merb application, or you are in " \ "a flat application and have not specified the init file. If you " \ "are trying to create a new merb application, use merb-gen app." end nil end # Expands Ruby path with framework directories (for models, lib, etc). Only run once. # # ==== Returns # nil # # :api: private def self.expand_ruby_path # Add models, controllers, helpers and lib to the load path unless @ran Merb.logger.info "Expanding RUBY_PATH..." if Merb::Config[:verbose] $LOAD_PATH.unshift Merb.dir_for(:model) $LOAD_PATH.unshift Merb.dir_for(:controller) $LOAD_PATH.unshift Merb.dir_for(:lib) $LOAD_PATH.unshift Merb.dir_for(:helper) end @ran = true nil end # Converts an absolute path to an path relative to Merbs root, if # the path is in the Merb root dir. Otherwise it will return the # absolute path. # # ==== Returns # String:: Relative path or absolute # # :api: private def self.relative_to_merb_path(path) absolute_path = File.expand_path(path) merb_root = File.expand_path(Merb.root) if absolute_path.slice(0, merb_root.length) == merb_root '.' + absolute_path.slice(merb_root.length..-1) else absolute_path end end end class Merb::BootLoader::MixinSession < Merb::BootLoader # Mixin the session functionality; this is done before BeforeAppLoads # so that SessionContainer and SessionStoreContainer can be subclassed by # plugin session stores for example - these need to be loaded in a # before_app_loads block or a BootLoader that runs after MixinSession. # # Note: access to Merb::Config is needed, so it needs to run after # Merb::BootLoader::Dependencies is done. # # ==== Returns # nil # # :api: plugin def self.run require 'merb-core/dispatch/session' Merb::Controller.send(:include, ::Merb::SessionMixin) Merb::Request.send(:include, ::Merb::SessionMixin::RequestMixin) end end class Merb::BootLoader::BeforeAppLoads < Merb::BootLoader # Call any before_app_loads hooks that were registered via before_app_loads # in any plugins. # # ==== Returns # nil # # :api: plugin def self.run Merb::BootLoader.before_load_callbacks.each { |x| x.call } nil end end # Load all classes inside the load paths. # # This is used in conjunction with Merb::BootLoader::ReloadClasses to track # files that need to be reloaded, and which constants need to be removed in # order to reload a file. # # This also adds the model, controller, and lib directories to the load path, # so they can be required in order to avoid load-order issues. class Merb::BootLoader::LoadClasses < Merb::BootLoader LOADED_CLASSES = {} MTIMES = {} FILES_LOADED = {} class << self # Load all classes from Merb's native load paths. # # If fork-based loading is used, every time classes are loaded this will return in a new spawner process # and boot loading will continue from this point in the boot loading process. # # If fork-based loading is not in use, this only returns once and does not fork a new # process. # # ==== Returns # Returns at least once: # nil # # :api: plugin def run # process name you see in ps output $0 = "merb#{" : " + Merb::Config[:name] if Merb::Config[:name]} : master" # Log the process configuration user defined signal 1 (SIGUSR1) is received. Merb.trap("USR1") do require "yaml" Merb.logger.fatal! "Configuration:\n#{Merb::Config.to_hash.merge(:pid => $$).to_yaml}\n\n" end if Merb::Config[:fork_for_class_load] && !Merb.testing? start_transaction else Merb.trap('INT') do Merb.logger.warn! "Reaping Workers" reap_workers end end # Load application file if it exists - for flat applications load_file Merb.dir_for(:application) if File.file?(Merb.dir_for(:application)) # Load classes and their requirements Merb.load_paths.each do |component, path| next if path.last.blank? || component == :application || component == :router load_classes(path.first / path.last) end Merb::Controller.send :include, Merb::GlobalHelpers nil end # Wait for any children to exit, remove the "main" PID, and # exit. # # ==== Returns # (Does not return.) # # :api: private def exit_gracefully # wait all workers to exit Process.waitall # remove master process pid Merb::Server.remove_pid("main") # terminate, workers remove their own pids # in on exit hook Merb::BootLoader.before_master_shutdown_callbacks.each do |cb| begin cb.call rescue Exception => e Merb.logger.fatal "before_master_shutdown callback crashed: #{e.message}" end end exit end # Set up the BEGIN point for fork-based loading and sets up # any signals in the parent and child. This is done by forking # the app. The child process continues on to run the app. The parent # process waits for the child process to finish and either forks again # # # ==== Returns # Parent Process: # (Does not return.) # Child Process returns at least once: # nil # # :api: private def start_transaction Merb.logger.warn! "Parent pid: #{Process.pid}" reader, writer = nil, nil # Enable REE garbage collection if GC.respond_to?(:copy_on_write_friendly=) GC.copy_on_write_friendly = true end loop do # create two connected endpoints # we use them for master/workers communication reader, @writer = IO.pipe pid = Kernel.fork # pid means we're in the parent; only stay in the loop if that is case break unless pid # writer must be closed so reader can generate EOF condition @writer.close # master process stores pid to merb.main.pid Merb::Server.store_pid("main") if Merb::Config[:daemonize] || Merb::Config[:cluster] if Merb::Config[:console_trap] Merb.trap("INT") {} else # send ABRT to worker on INT Merb.trap("INT") do Merb.logger.warn! "Reaping Workers" begin Process.kill(reap_workers_signal, pid) rescue SystemCallError end exit_gracefully end end Merb.trap("HUP") do Merb.logger.warn! "Doing a fast deploy\n" Process.kill("HUP", pid) end reader_ary = [reader] loop do # wait for worker to exit and capture exit status # # # WNOHANG specifies that wait2 exists without waiting # if no worker processes are ready to be noticed. if exit_status = Process.wait2(pid, Process::WNOHANG) # wait2 returns a 2-tuple of process id and exit # status. # # We do not care about specific pid here. exit_status[1] && exit_status[1].exitstatus == 128 ? break : exit end # wait for data to become available, timeout in 0.5 of a second if select(reader_ary, nil, nil, 0.5) begin # no open writers next if reader.eof? msg = reader.readline reader.close if msg.to_i == 128 Process.waitpid(pid, Process::WNOHANG) break else exit_gracefully end rescue SystemCallError exit_gracefully end end end end reader.close # add traps to the worker if Merb::Config[:console_trap] Merb::Server.add_irb_trap at_exit { reap_workers } else Merb.trap('INT') do Merb::BootLoader.before_worker_shutdown_callbacks.each { |cb| cb.call } end Merb.trap('ABRT') { reap_workers } Merb.trap('HUP') { reap_workers(128, "ABRT") } end end def reap_workers_signal Merb::Config[:reap_workers_quickly] ? "KILL" : "ABRT" end # Reap any workers of the spawner process and # exit with an appropriate status code. # # Note that exiting the spawner process with a status code # of 128 when a master process exists will cause the # spawner process to be recreated, and the app code reloaded. # # ==== Parameters # status:: The status code to exit with. Defaults to 0. # sig:: The signal to send to workers # # ==== Returns # (Does not return.) # # :api: private def reap_workers(status = 0, sig = reap_workers_signal) Merb.logger.info "Executed all before worker shutdown callbacks..." Merb::BootLoader.before_worker_shutdown_callbacks.each do |cb| begin cb.call rescue Exception => e Merb.logger.fatal "before worker shutdown callback crashed: #{e.message}" end end Merb.exiting = true unless status == 128 begin if @writer @writer.puts(status.to_s) @writer.close end rescue SystemCallError end threads = [] ($WORKERS || []).each do |p| threads << Thread.new do begin Process.kill(sig, p) Process.wait2(p) rescue SystemCallError end end end threads.each {|t| t.join } exit(status) end # Loads a file, tracking its modified time and, if necessary, the classes it declared. # # ==== Parameters # file:: The file to load. # # ==== Returns # nil # # :api: private def load_file(file, reload = false) Merb.logger.verbose! "#{reload ? "re" : ""}loading #{file}" # If we're going to be reloading via constant remove, # keep track of what constants were loaded and what files # have been added, so that the constants can be removed # and the files can be removed from $LOADED_FEAUTRES if !Merb::Config[:fork_for_class_load] if FILES_LOADED[file] FILES_LOADED[file].each {|lf| $LOADED_FEATURES.delete(lf)} end klasses = ObjectSpace.classes.dup files_loaded = $LOADED_FEATURES.dup end # If we're in the midst of a reload, remove the file # itself from $LOADED_FEATURES so it will get reloaded if reload $LOADED_FEATURES.delete(file) if reload end # Ignore the file for syntax errors. The next time # the file is changed, it'll be reloaded again begin require file rescue SyntaxError => e Merb.logger.error "Cannot load #{file} because of syntax error: #{e.message}" ensure if Merb::Config[:reload_classes] MTIMES[file] = File.mtime(file) end end # If we're reloading via constant remove, store off the details # after the file has been loaded unless Merb::Config[:fork_for_class_load] LOADED_CLASSES[file] = ObjectSpace.classes - klasses FILES_LOADED[file] = $LOADED_FEATURES - files_loaded end nil end # Load classes from given paths - using path/glob pattern. # # ==== Parameters # *paths:: # Array of paths to load classes from - may contain glob pattern # # ==== Returns # nil # # :api: private def load_classes(*paths) orphaned_classes = [] paths.flatten.each do |path| Dir[path].sort.each do |file| begin load_file file rescue NameError => ne Merb.logger.verbose! "Stashed file with missing requirements for later reloading: #{file}" ne.backtrace.each_with_index { |line, idx| Merb.logger.verbose! "[#{idx}]: #{line}" } orphaned_classes.unshift(file) end end end load_classes_with_requirements(orphaned_classes) end # Reloads the classes in the specified file. If fork-based loading is used, # this causes the current processes to be killed and and all classes to be # reloaded. If class-based loading is not in use, the classes declared in that file # are removed and the file is reloaded. # # ==== Parameters # file:: The file to reload. # # ==== Returns # When fork-based loading is used: # (Does not return.) # When fork-based loading is not in use: # nil # # :api: private def reload(file) if Merb::Config[:fork_for_class_load] reap_workers(128) else remove_classes_in_file(file) { |f| load_file(f, true) } end end # Removes all classes declared in the specified file. Any hashes which use classes as keys # will be protected provided they have been added to Merb.klass_hashes. These hashes have their # keys substituted with placeholders before the file's classes are unloaded. If a block is provided, # it is called before the substituted keys are reconstituted. # # ==== Parameters # file:: The file to remove classes for. # &block:: A block to call with the file that has been removed before klass_hashes are updated # to use the current values of the constants they used as keys. # # ==== Returns # nil # # :api: private def remove_classes_in_file(file, &block) Merb.klass_hashes.each { |x| x.protect_keys! } if klasses = LOADED_CLASSES.delete(file) klasses.each { |klass| remove_constant(klass) unless klass.to_s =~ /Router/ } end yield file if block_given? Merb.klass_hashes.each {|x| x.unprotect_keys!} nil end # Removes the specified class. # # Additionally, removes the specified class from the subclass list of every superclass that # tracks it's subclasses in an array returned by _subclasses_list. Classes that wish to use this # functionality are required to alias the reader for their list of subclasses # to _subclasses_list. Plugins for ORMs and other libraries should keep this in mind. # # ==== Parameters # const:: The class to remove. # # ==== Returns # nil # # :api: private def remove_constant(const) # This is to support superclasses (like AbstractController) that track # their subclasses in a class variable. superklass = const until (superklass = superklass.superclass).nil? if superklass.respond_to?(:_subclasses_list) superklass.send(:_subclasses_list).delete(klass) superklass.send(:_subclasses_list).delete(klass.to_s) end end parts = const.to_s.split("::") base = parts.size == 1 ? Object : Object.full_const_get(parts[0..-2].join("::")) object = parts[-1].to_s begin base.send(:remove_const, object) Merb.logger.debug("Removed constant #{object} from #{base}") rescue NameError Merb.logger.debug("Failed to remove constant #{object} from #{base}") end nil end private # "Better loading" of classes. If a file fails to load due to a NameError # it will be added to the failed_classes and load cycle will be repeated unless # no classes load. # # ==== Parameters # klasses:: Classes to load. # # ==== Returns # nil # # :api: private def load_classes_with_requirements(klasses) klasses.uniq! while klasses.size > 0 # Note size to make sure things are loading size_at_start = klasses.size # List of failed classes failed_classes = [] # Map classes to exceptions error_map = {} klasses.each do |klass| begin load_file klass rescue NameError => ne error_map[klass] = ne failed_classes.push(klass) end end klasses.clear # Keep list of classes unique failed_classes.each { |k| klasses.push(k) unless klasses.include?(k) } # Stop processing if nothing loads or if everything has loaded if klasses.size == size_at_start && klasses.size != 0 # Write all remaining failed classes and their exceptions to the log messages = error_map.only(*failed_classes).map do |klass, e| ["Could not load #{klass}:\n\n#{e.message} - (#{e.class})", "#{(e.backtrace || []).join("\n")}"] end messages.each { |msg, trace| Merb.logger.fatal!("#{msg}\n\n#{trace}") } Merb.fatal! "#{failed_classes.join(", ")} failed to load." end break if(klasses.size == size_at_start || klasses.size == 0) end nil end end end # Loads the router file. This needs to happen after everything else is loaded while merb is starting up to ensure # the router has everything it needs to run. class Merb::BootLoader::Router < Merb::BootLoader class << self # load the router file # # ==== Returns # nil # # :api: plugin def run Merb::BootLoader::LoadClasses.load_file(router_file) if router_file nil end # Tries to find the router file. # # ==== Returns # String:: The path to the router file if it exists, nil otherwise. # # :api: private def router_file @router_file ||= begin if File.file?(router = Merb.dir_for(:router) / Merb.glob_for(:router)) router end end end end end # Precompiles all non-partial templates. class Merb::BootLoader::Templates < Merb::BootLoader class << self # Loads all non-partial templates into the Merb::InlineTemplates module. # # ==== Returns # Array[String]:: The list of template files which were loaded. # # :api: plugin def run template_paths.each do |path| Merb::Template.inline_template(File.open(path)) end end # Finds a list of templates to load. # # ==== Returns # Array[String]:: All found template files whose basename does not begin with "_". # # :api: private def template_paths extension_glob = "{#{Merb::Template.template_extensions.join(',')}}" # This gets all templates set in the controllers template roots # We separate the two maps because most of controllers will have # the same _template_root, so it's silly to be globbing the same # path over and over. controller_view_paths = [] Merb::AbstractController._abstract_subclasses.each do |klass| next if (const = Object.full_const_get(klass))._template_root.blank? controller_view_paths += const._template_roots.map { |pair| pair.first } end template_paths = controller_view_paths.uniq.compact.map { |path| Dir["#{path}/**/*.#{extension_glob}"] } # This gets the templates that might be created outside controllers # template roots. eg app/views/shared/* template_paths << Dir["#{Merb.dir_for(:view)}/**/*.#{extension_glob}"] if Merb.dir_for(:view) # This ignores templates for partials, which need to be compiled at use time to generate # a preamble that assigns local variables template_paths.flatten.compact.uniq.grep(%r{^.*/[^_][^/]*$}) end end end # Register the default MIME types: # # By default, the mime-types include: # :all:: no transform, */* # :yaml:: to_yaml, application/x-yaml or text/yaml # :text:: to_text, text/plain # :html:: to_html, text/html or application/xhtml+xml or application/html # :xml:: to_xml, application/xml or text/xml or application/x-xml # :js:: to_json, text/javascript ot application/javascript or application/x-javascript # :json:: to_json, application/json or text/x-json class Merb::BootLoader::MimeTypes < Merb::BootLoader # Registers the default MIME types. # # ==== Returns # nil # # :api: plugin def self.run Merb.add_mime_type(:all, nil, %w[*/*]) Merb.add_mime_type(:yaml, :to_yaml, %w[application/x-yaml text/yaml], :charset => "utf-8") Merb.add_mime_type(:text, :to_text, %w[text/plain], :charset => "utf-8") Merb.add_mime_type(:html, :to_html, %w[text/html application/xhtml+xml application/html], :charset => "utf-8") Merb.add_mime_type(:xml, :to_xml, %w[application/xml text/xml application/x-xml], {:charset => "utf-8"}, 0.9998) Merb.add_mime_type(:js, :to_json, %w[text/javascript application/javascript application/x-javascript], :charset => "utf-8") Merb.add_mime_type(:json, :to_json, %w[application/json text/x-json], :charset => "utf-8") nil end end # Set up cookies support in Merb::Controller and Merb::Request class Merb::BootLoader::Cookies < Merb::BootLoader # Set up cookies support in Merb::Controller and Merb::Request # # ==== Returns # nil # # :api: plugin def self.run require 'merb-core/dispatch/cookies' Merb::Controller.send(:include, Merb::CookiesMixin) Merb::Request.send(:include, Merb::CookiesMixin::RequestMixin) nil end end class Merb::BootLoader::SetupSession < Merb::BootLoader # Enable the configured session container(s); any class that inherits from # SessionContainer will be considered by its session_store_type attribute. # # ==== Returns # nil # # :api: plugin def self.run # Require all standard session containers. Dir[Merb.framework_root / "merb-core" / "dispatch" / "session" / "*.rb"].each do |file| base_name = File.basename(file, ".rb") require file unless base_name == "container" || base_name == "store_container" end # Set some defaults. Merb::Config[:session_id_key] ||= "_session_id" # List of all session_stores from :session_stores and :session_store config options. config_stores = Merb::Config.session_stores # Register all configured session stores - any loaded session container class # (subclassed from Merb::SessionContainer) will be available for registration. Merb::SessionContainer.subclasses.each do |class_name| if(store = Object.full_const_get(class_name)) && config_stores.include?(store.session_store_type) Merb::Request.register_session_type(store.session_store_type, class_name) end end # Mixin the Merb::Session module to add app-level functionality to sessions overrides = (Merb::Session.instance_methods & Merb::SessionContainer.instance_methods) overrides.each do |m| Merb.logger.warn!("Warning: Merb::Session##{m} overrides existing " \ "Merb::SessionContainer##{m}") end Merb::SessionContainer.send(:include, Merb::Session) nil end end # In case someone's running a sparse app, the default exceptions require the # Exceptions class. This must run prior to the AfterAppLoads BootLoader # So that plugins may have ensured access in the after_app_loads block class Merb::BootLoader::SetupStubClasses < Merb::BootLoader # Declares empty Application and Exception controllers. # # ==== Returns # nil # # :api: plugin def self.run unless defined?(Exceptions) Object.class_eval <<-RUBY class Application < Merb::Controller abstract! end class Exceptions < Merb::Controller end RUBY end nil end end class Merb::BootLoader::AfterAppLoads < Merb::BootLoader # Call any after_app_loads hooks that were registered via after_app_loads in # init.rb. # # ==== Returns # nil # # :api: plugin def self.run Merb::BootLoader.after_load_callbacks.each {|x| x.call } nil end end class Merb::BootLoader::ChooseAdapter < Merb::BootLoader # Choose the Rack adapter/server to use and set Merb.adapter. # # ==== Returns # nil # # :api: plugin def self.run # Check if we running in IRB if so run IRB adapter Merb::Config[:adapter] = 'irb' if Merb.running_irb? Merb.adapter = Merb::Rack::Adapter.get(Merb::Config[:adapter]) end end class Merb::BootLoader::RackUpApplication < Merb::BootLoader # Setup the Merb Rack App or read a rackup file located at # Merb::Config[:rackup] with the same syntax as the # rackup tool that comes with rack. Automatically evals the file in # the context of a Rack::Builder.new { } block. Allows for mounting # additional apps or middleware. # # ==== Returns # nil # # :api: plugin def self.run require 'rack' if File.exists?(Merb.dir_for(:config) / "rack.rb") Merb::Config[:rackup] ||= Merb.dir_for(:config) / "rack.rb" end if Merb::Config[:rackup] rackup_code = File.read(Merb::Config[:rackup]) Merb::Config[:app] = eval("::Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, Merb::Config[:rackup]) else Merb::Config[:app] = ::Rack::Builder.new { use Merb::Rack::Head # handle head requests use Merb::Rack::ContentLength # report content length if prefix = ::Merb::Config[:path_prefix] use Merb::Rack::PathPrefix, prefix end use Merb::Rack::Static, Merb.dir_for(:public) run Merb::Rack::Application.new }.to_app end nil end end class Merb::BootLoader::BackgroundServices < Merb::BootLoader # Start background services, such as the run_later worker thread. # # ==== Returns # nil # # :api: plugin def self.run Merb::Worker.start unless Merb.testing? || Merb::Worker.started? nil end end class Merb::BootLoader::ReloadClasses < Merb::BootLoader class TimedExecutor # Executes the associated block every @seconds@ seconds in a separate thread. # # ==== Parameters # seconds:: Number of seconds to sleep in between runs of &block. # &block:: The block to execute periodically. # # ==== Returns # Thread:: The thread executing the block periodically. # # :api: private def self.every(seconds, &block) Thread.new do loop do sleep( seconds ) yield end Thread.exit end end end # Set up the class reloader if class reloading is enabled. This checks periodically # for modifications to files loaded by the LoadClasses BootLoader and reloads them # when they are modified. # # ==== Returns # nil # # :api: plugin def self.run return unless Merb::Config[:reload_classes] TimedExecutor.every(Merb::Config[:reload_time] || 0.5) do GC.start reload! end nil end # Reloads all the files on the Merb application path # # ==== Returns # nil # # :api: private def self.reload! reload(build_paths) end # Reloads all files which have been modified since they were last loaded. # # ==== Returns # nil # # :api: private def self.reload(paths = []) paths.each do |file| next if LoadClasses::MTIMES[file] && LoadClasses::MTIMES[file] == File.mtime(file) LoadClasses.reload(file) end nil end # Returns a list of the paths on the merb application stack # # ==== Returns # nil # # :api: private def self.build_paths paths = [] Merb.load_paths.each do |path_name, file_info| path, glob = file_info next unless glob paths << Dir[path / glob] end if Merb.dir_for(:application) && File.file?(Merb.dir_for(:application)) paths << Merb.dir_for(:application) end paths.flatten! return paths end end