%w[css js vcs].each do |filename|
require File.join(File.dirname(__FILE__), 'asset_hat', filename)
end
# Your assets are covered. See README.rdoc for more.
module AssetHat
if defined?(Rails) && Rails::VERSION::MAJOR >= 3
RAILS_ROOT = Rails.root || '.' #:nodoc:
require 'asset_hat/railtie'
else
RAILS_ROOT = File.join(File.dirname(__FILE__), '..') unless
defined?(RAILS_ROOT) #:nodoc:
end
# Types of supported assets; currently [:css, :js]
.
TYPES = [:css, :js]
# Base directory in which all assets are kept, e.g., 'public/'.
ASSETS_DIR = defined?(Rails.public_path) && Rails.public_path.present? ?
Rails.public_path : 'public'
# Root URL path for all stylesheets. For public-facing use.
STYLESHEETS_PATH = '/stylesheets'
# Root URL path for all JavaScripts. For public-facing use.
JAVASCRIPTS_PATH = '/javascripts'
# Directory in which all stylesheets are kept, e.g., 'public/stylesheets'.
# For internal filesystem use.
STYLESHEETS_DIR = File.join(ASSETS_DIR, 'stylesheets')
# Directory in which all JavaScripts are kept, e.g., 'public/javascripts'.
# For internal filesystem use.
JAVASCRIPTS_DIR = File.join(ASSETS_DIR, 'javascripts')
# Relative path for the config file.
RELATIVE_CONFIG_FILEPATH = File.join('config', 'assets.yml')
# Absolute path for the config file.
CONFIG_FILEPATH = File.join(RAILS_ROOT, RELATIVE_CONFIG_FILEPATH)
class << self
attr_accessor :config, :asset_exists, :html_cache #:nodoc:
end
# Nested-hash version of config/assets.yml
.
def self.config
unless File.exists?(CONFIG_FILEPATH)
raise '`config/assets.yml` is missing! ' +
'Run `rake asset_hat:config` to generate it.' and return
end
if !cache? || @config.blank?
@config = YAML.load(ERB.new(File.read(CONFIG_FILEPATH)).result)
end
@config
end
# Returns the relative path to the directory where the original CSS or JS
# files are stored. For internal filesystem use.
#
# type
argument: :css
or :js
def self.assets_dir(type)
case type.to_sym
when :css ; STYLESHEETS_DIR
when :js ; JAVASCRIPTS_DIR
else
raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
nil
end
end
# Returns the root URL path where the original CSS or JS files are stored.
# For external URL-building use.
#
# type
argument: :css
or :js
def self.assets_path(type)
case type.to_sym
when :css ; STYLESHEETS_PATH
when :js ; JAVASCRIPTS_PATH
else
raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
nil
end
end
# Returns the relative path to the directory where CSS or JS bundles are
# stored. For internal filesystem use.
#
# Usage:
#
# AssetHat.bundles_dir
# # => 'bundles'
# AssetHat.bundles_dir(:ssl => true)
# # => 'bundles/ssl'
# AssetHat.bundles_dir(:css)
# # => 'public/stylesheets/bundles'
# AssetHat.bundles_dir(:js, :ssl => true)
# # => 'public/javascripts/bundles/ssl
#
# Options:
#
# [ssl] Set this to true
if the stylesheet references images
# via SSL. Defaults to false
.
def self.bundles_dir(*args)
options = args.extract_options!
options.symbolize_keys!.reverse_merge!(:ssl => false)
type = args.first
dir = type.present? ? File.join(assets_dir(type), 'bundles') : 'bundles'
dir = File.join(dir, 'ssl') if options[:ssl]
dir
end
# Returns the root URL path where CSS or JS bundles are stored. For external
# URL-building use.
#
# Usage:
#
# AssetHat.bundles_path(:css)
# # => 'public/stylesheets/bundles'
# AssetHat.bundles_path(:js, :ssl => true)
# # => 'public/javascripts/bundles/ssl
#
# Options:
#
# [ssl] Set this to true
if the stylesheet references images
# via SSL. Defaults to false
.
def self.bundles_path(type, options={})
type = type.to_sym
unless TYPES.include?(type)
raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
return
end
path = case type
when :css ; STYLESHEETS_PATH
when :js ; JAVASCRIPTS_PATH
else nil
end
path += '/bundles'
path += '/ssl' if options[:ssl]
path
end
# Returns true if the specified asset exists in the file system:
#
# AssetHat.asset_exists?('application.css', :css)
# # => true if public/stylesheets/application.css exists
# AssetHat.asset_exists?('some-plugin.js', :js)
# # => true if public/javascripts/some-plugin.js exists
#
# See also AssetHat.assets_dir
.
def self.asset_exists?(filename, type)
# Process arguments
type = type.to_sym
unless TYPES.include?(type)
raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
return
end
# Default to `{:css => {}, :js => {}}`
@asset_exists ||= TYPES.inject({}) { |hsh, t| hsh.merge(t => {}) }
if @asset_exists[type][filename].nil?
@asset_exists[type][filename] =
File.exist?(File.join(self.assets_dir(type), filename))
end
@asset_exists[type][filename]
end
# Returns true
if bundles should be included as single minified
# files (e.g., in production), or false
if bundles should be
# included as separate, unminified files (e.g., in development). To modify
# this value, set
# config.action_controller.perform_caching
(boolean)
# in your environment.
def self.cache? ; ActionController::Base.perform_caching ; end
# Returns the value of
# Rails.application.config.consider_all_requests_local
or its
# equivalent in older versions of Rails. To modify this value, set
# config.consider_all_requests_local
(boolean) in your
# environment.
def self.consider_all_requests_local?
if defined?(Rails) && Rails.respond_to?(:application)
Rails.application.config.consider_all_requests_local
else # Rails 2.x
ActionController::Base.consider_all_requests_local
end
end
# Returns the expected path for the minified version of an asset:
#
# AssetHat.min_filepath('public/stylesheets/bundles/application.css', 'css')
# # => 'public/stylesheets/bundles/application.min.css'
#
# See also AssetHat::CSS.min_filepath
and
# AssetHat::JS.min_filepath
.
def self.min_filepath(filepath, extension)
filepath.sub(/([^\.]*).#{extension}$/, "\\1.min.#{extension}")
end
# Returns the extension-less names of files in the given bundle:
#
# AssetHat.bundle_filenames('application', :css)
# # => ['reset', 'application']
# AssetHat.bundle_filenames('non-existent-file', :css)
# # => nil
def self.bundle_filenames(bundle, type)
# Process arguments
unless TYPES.include?(type.to_sym)
raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
return
end
self.config[type.to_s]['bundles'][bundle.to_s] rescue nil
end
# Returns the full paths of files in the given bundle:
#
# AssetHat.bundle_filenames('application', :css)
# # => ['/path/to/app/public/stylesheets/reset.css',
# '/path/to/app/public/stylesheets/application.css']
# AssetHat.bundle_filenames('non-existent-file', :css)
# # => nil
def self.bundle_filepaths(bundle, type)
# Process arguments
unless TYPES.include?(type.to_sym)
raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
return
end
dir = self.assets_dir(type)
filenames = self.bundle_filenames(bundle, type)
filepaths = filenames.present? ?
filenames.map { |fn| File.join(dir, "#{fn}.#{type}") } : nil
end
# Reads ActionController::Base.asset_host
, which can be a
# String or Proc, and returns a String. Should behave just like Rails
# 2.3.x's private `compute_asset_host` method, but with a simulated request.
#
# Example environment config for CDN support via SSL:
#
# # In config/environments/production.rb:
# config.action_controller.asset_host = Proc.new do |source, request|
# "#{request.protocol}cdn#{source.hash % 4}.example.com"
# # => 'http://cdn0.example.com', 'https://cdn1.example.com', etc.
# end
#
# If your CDN doesn't have SSL support, you can instead revert SSL pages to
# serving assets from your web server:
#
# config.action_controller.asset_host = Proc.new do |source, request|
# request.ssl? ? nil : "http://cdn#{source.hash % 4}.example.com"
# end
#
# Options:
#
# [ssl] Set to true
to simulate a request via SSL. Defaults to
# false
.
def self.compute_asset_host(asset_host, source, options={})
host = asset_host
if host.is_a?(Proc) || host.respond_to?(:call)
case host.is_a?(Proc) ?
host.arity : host.method(:call).arity
when 2
if defined?(ActionDispatch)
request_class = ActionDispatch::Request
else # Rails 2.x
request_class = ActionController::Request
end
request = request_class.new(
'HTTPS' => options[:ssl] ? 'on' : 'off')
host = host.call(source, request)
else
host = host.call(source)
end
else
host %= (source.hash % 4) if host =~ /%d/
end
host
end
# Returns true
if the asset host differs between SSL and
# non-SSL pages, or false
if the asset host doesn't change.
def self.ssl_asset_host_differs?
asset_host = ActionController::Base.asset_host
AssetHat.compute_asset_host(asset_host, 'x.png') !=
AssetHat.compute_asset_host(asset_host, 'x.png', :ssl => true)
end
def self.clear_html_cache
html_cache = {}
end
end