require 'zlib' require 'active_support/core_ext/file' require 'action_controller/metal/exceptions' module ActionView class AssetPaths #:nodoc: attr_reader :config, :controller def initialize(config, controller = nil) @config = config @controller = controller end # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched. # Prefix with /dir/ if lacking a leading +/+. Account for relative URL # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. # # When :relative (default), the protocol will be determined by the client using current protocol # When :request, the protocol will be the request protocol # Otherwise, the protocol is used (E.g. :http, :https, etc) def compute_public_path(source, dir, options = {}) source = source.to_s return source if is_uri?(source) source = rewrite_extension(source, dir, options[:ext]) if options[:ext] source = rewrite_asset_path(source, dir, options) source = rewrite_relative_url_root(source, relative_url_root) source = rewrite_host_and_protocol(source, options[:protocol]) source end # Return the filesystem path for the source def compute_source_path(source, dir, ext) source = rewrite_extension(source, dir, ext) if ext File.join(config.assets_dir, dir, source) end def is_uri?(path) path =~ %r{^[-a-z]+://|^cid:|^//} end private def rewrite_extension(source, dir, ext) raise NotImplementedError end def rewrite_asset_path(source, path = nil) raise NotImplementedError end def rewrite_relative_url_root(source, relative_url_root) relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source end def has_request? controller.respond_to?(:request) end def rewrite_host_and_protocol(source, protocol = nil) host = compute_asset_host(source) if host && !is_uri?(host) if (protocol || default_protocol) == :request && !has_request? host = nil else host = "#{compute_protocol(protocol)}#{host}" end end host ? "#{host}#{source}" : source end def compute_protocol(protocol) protocol ||= default_protocol case protocol when :relative "//" when :request unless @controller invalid_asset_host!("The protocol requested was :request. Consider using :relative instead.") end @controller.request.protocol else "#{protocol}://" end end def default_protocol @config.default_asset_host_protocol || (has_request? ? :request : :relative) end def invalid_asset_host!(help_message) raise ActionController::RoutingError, "This asset host cannot be computed without a request in scope. #{help_message}" end # Pick an asset host for this source. Returns +nil+ if no host is set, # the host if no wildcard is set, the host interpolated with the # numbers 0-3 if it contains %d (the number is the source hash mod 4), # or the value returned from invoking call on an object responding to call # (proc or otherwise). def compute_asset_host(source) if host = asset_host_config if host.respond_to?(:call) args = [source] arity = arity_of(host) if (arity > 1 || arity < -2) && !has_request? invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request, or make it optional.") end args << current_request if (arity > 1 || arity < 0) && has_request? host.call(*args) else (host =~ /%d/) ? host % (Zlib.crc32(source) % 4) : host end end end def relative_url_root config.relative_url_root end def asset_host_config config.asset_host end # Returns the current request if one exists. def current_request controller.request if has_request? end # Returns the arity of a callable def arity_of(callable) callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity end end end