padrino-core/lib/padrino-core/mounter.rb
require 'padrino-core/mounter/application_extension'
module Padrino
##
# Represents a particular mounted Padrino application.
# Stores the name of the application (app folder name) and url mount path.
#
# @example
# Mounter.new("blog_app", :app_class => "Blog").to("/blog")
# Mounter.new("blog_app", :app_file => "/path/to/blog/app.rb").to("/blog")
#
class Mounter
DEFAULT_CASCADE = [404, 405]
class MounterException < RuntimeError
end
attr_accessor :name, :uri_root, :app_file, :app_class, :app_root, :app_obj, :app_host, :cascade
##
# @param [String, Padrino::Application] name
# The app name or the {Padrino::Application} class.
#
# @param [Hash] options
# @option options [Symbol] :app_class (Detected from name)
# @option options [Symbol] :app_file (Automatically detected)
# @option options [Symbol] :app_obj (Detected)
# @option options [Symbol] :app_root (Directory of :app_file)
# @option options [Symbol] :gem The gem to load the app from (Detected from name)
#
def initialize(name, options={})
@name = name.to_s
@app_class = options[:app_class] || Inflections.camelize(@name)
@gem = options[:gem] || Inflections.underscore(@app_class.split("::").first)
@app_file = options[:app_file] || locate_app_file
@app_obj = options[:app_obj] || app_constant || locate_app_object
ensure_app_file! || ensure_app_object!
unless padrino_application?
@app_obj.extend ApplicationExtension
@app_obj.mounter_options = options
end
@app_root = options[:app_root] || (@app_obj.respond_to?(:root) && @app_obj.root || File.dirname(@app_file))
@uri_root = "/"
@cascade = options[:cascade] ? true == options[:cascade] ? DEFAULT_CASCADE.dup : Array(options[:cascade]) : []
Padrino::Reloader.exclude_constants << @app_class
end
def padrino_application?
@app_obj.ancestors.include?(Padrino::Application)
rescue NameError
false
end
##
# Registers the mounted application onto Padrino.
#
# @param [String] mount_url
# Path where we mount the app.
#
# @example
# Mounter.new("blog_app").to("/blog")
#
def to(mount_url)
@uri_root = mount_url
Padrino.insert_mounted_app(self)
self
end
##
# Registers the mounted application onto Padrino for the given host.
#
# @param [String] mount_host
# Host name.
#
# @example
# Mounter.new("blog_app").to("/blog").host("blog.padrino.org")
# Mounter.new("blog_app").host("blog.padrino.org")
# Mounter.new("catch_all").host(/.*\.padrino.org/)
#
def host(mount_host)
@app_host = mount_host
Padrino.insert_mounted_app(self)
self
end
##
# Maps Padrino application onto a Padrino::Router.
# For use in constructing a Rack application.
#
# @param [Padrino::Router]
#
# @return [Padrino::Router]
#
# @example
# @app.map_onto(router)
#
def map_onto(router)
app_data = self
app_obj = @app_obj
uri_root = app_data.uri_root
public_folder_exists = File.exist?(app_obj.public_folder)
if padrino_application?
app_obj.set :uri_root, uri_root
app_obj.set :app_name, app_obj.app_name.to_s
app_obj.set :root, app_data.app_root
app_obj.set :public_folder, Padrino.root('public', uri_root) unless public_folder_exists
app_obj.set :static, public_folder_exists
app_obj.set :cascade, app_data.cascade
else
app_obj.cascade = app_data.cascade
app_obj.uri_root = uri_root
app_obj.public_folder = Padrino.root('public', uri_root) unless public_folder_exists
end
app_obj.setup_application! # Initializes the app here with above settings.
router.map(:to => app_obj, :path => uri_root, :host => app_data.app_host)
end
###
# Returns the route objects for the mounted application.
#
def routes
app_obj.routes
end
###
# Returns the basic route information for each named route.
#
# @return [Array]
# Array of routes.
#
def named_routes
return [] unless app_obj.respond_to?(:routes)
app_obj.routes.map { |route|
request_method = route.request_methods.first
next if !route.name || request_method == 'HEAD'
route_name = route.name.to_s
route_name =
if route.controller
route_name.split(" ", 2).map{|name| ":#{name}" }.join(", ")
else
":#{route_name}"
end
name_array = "(#{route_name})"
original_path = route.original_path.is_a?(Regexp) ? route.original_path.inspect : route.original_path
full_path = File.join(uri_root, original_path)
OpenStruct.new(:verb => request_method, :identifier => route.name, :name => name_array, :path => full_path)
}.compact
end
##
# Makes two Mounters equal if they have the same name and uri_root.
#
# @param [Padrino::Mounter] other
#
def ==(other)
other.is_a?(Mounter) && self.app_class == other.app_class && self.uri_root == other.uri_root
end
##
# @return [Padrino::Application]
# the class object for the app if defined, nil otherwise.
#
def app_constant
klass = Object
for piece in app_class.split("::")
piece = piece.to_sym
if klass.const_defined?(piece, false)
klass = klass.const_get(piece)
else
return
end
end
klass
end
protected
##
# Locates and requires the file to load the app constant.
#
def locate_app_object
@_app_object ||= begin
ensure_app_file!
Padrino.require_dependencies(app_file)
app_constant
end
end
##
# Returns the determined location of the mounted application main file.
#
def locate_app_file
app_const = app_constant
candidates = []
candidates << app_const.app_file if app_const.respond_to?(:app_file)
candidates << Padrino.first_caller if File.identical?(Padrino.first_caller.to_s, Padrino.called_from.to_s)
candidates << Padrino.mounted_root(name.downcase, "app.rb")
simple_name = name.split("::").last.downcase
mod_name = name.split("::")[0..-2].join("::")
Padrino.modules.each do |mod|
if mod.name == mod_name
candidates << mod.root(simple_name, "app.rb")
end
end
candidates << Padrino.root("app", "app.rb")
candidates.find { |candidate| File.exist?(candidate) }
end
###
# Raises an exception unless app_file is located properly.
#
def ensure_app_file!
message = "Unable to locate source file for app '#{app_class}', try with :app_file => '/path/app.rb'"
raise MounterException, message unless @app_file
end
###
# Raises an exception unless app_obj is defined properly.
#
def ensure_app_object!
message = "Unable to locate app for '#{app_class}', try with :app_class => 'MyAppClass'"
raise MounterException, message unless @app_obj
end
end
class << self
attr_writer :mounted_root # Set root directory where padrino searches mounted apps
##
# @param [Array] args
#
# @return [String]
# the root to the mounted apps base directory.
#
def mounted_root(*args)
Padrino.root(@mounted_root ||= "", *args)
end
##
# @return [Array]
# the mounted padrino applications (MountedApp objects)
#
def mounted_apps
@mounted_apps ||= []
end
##
# Inserts a Mounter object into the mounted applications (avoids duplicates).
#
# @param [Padrino::Mounter] mounter
#
def insert_mounted_app(mounter)
Padrino.mounted_apps.push(mounter) unless Padrino.mounted_apps.include?(mounter)
end
##
# Mounts a new sub-application onto Padrino project.
#
# @see Padrino::Mounter#new
#
# @example
# Padrino.mount("blog_app").to("/blog")
#
def mount(name, options={})
Mounter.new(name, options)
end
end
end