core/lib/compass/core/asset_url_resolver.rb
class Compass::Core::AssetUrlResolver
include Compass::Core::HTTPUtil
class AssetNotFound < Sass::SyntaxError
def initialize(type, path, asset_collections)
asset_search_paths = asset_collections.map{|ac| ac.send(:"#{type}s_path") }
super("Could not find #{path} in #{ asset_search_paths.join(", ")}")
end
end
attr_accessor :asset_collections
attr_accessor :configuration
def initialize(asset_collections, configuration = nil)
@configuration = configuration || Compass.configuration
@asset_collections = asset_collections.dup
unless @asset_collections.find {|ac| Compass::Configuration::DefaultAssetCollection === ac }
@asset_collections.unshift(Compass::Configuration::DefaultAssetCollection.new)
end
if configuration
@asset_collections.each {|ac| ac.configuration = configuration }
end
end
# Compute a url for a given asset type (:image or :font)
def compute_url(type, relative_path, relative_to_css_url = nil, use_cache_buster = true)
# pass through fully specified urls
return relative_path if relative_path.start_with?("http://")
clean_relative_path, query, target = clean_path(relative_path)
# Find the asset collection that includes this asset
asset_collection, clean_relative_path, real_path = find_collection(type, clean_relative_path)
# Didn't find the asset, but it's a full url so just return it.
return relative_path if asset_collection.nil? && relative_path.start_with?("/")
# Raise an error for relative paths if we didn't find it on the search path.
raise AssetNotFound.new(type, relative_path, @asset_collections) unless asset_collection
# Make a root-relative url (starting with /)
asset_url = url_join(asset_collection.send(:"http_#{type}s_path"), clean_relative_path)
# Compute asset cache buster
busted_path, busted_query = cache_buster(asset_collection, asset_url, real_path) if use_cache_buster
asset_url = busted_path if busted_path
query = [query, busted_query].compact.join("&") if busted_query
# Convert path to a relative url if a css file is specified.
relative_url = compute_relative_path(relative_to_css_url, asset_url) if relative_to_css_url
# Compute asset host when not relative and one is provided
asset_host = if asset_collection.asset_host && relative_url.nil?
asset_collection.asset_host.call(asset_url)
end
# build the full url
url = url_join(asset_host || "", relative_url || asset_url)
url << "?" if query
url << query if query
url << "#" if target
url << target if target
return url
end
# Find the real path to a relative asset path
def find_asset(type, relative_path)
clean_relative_path, _, _ = clean_path(relative_path)
_, _, real_path = find_collection(type, clean_relative_path)
return real_path
end
def relative_path(type, absolute_path)
absolute_path = File.expand_path(absolute_path)
@asset_collections.find do |ac|
asset_path = File.expand_path(ac.send(:"#{type}s_path"))
if absolute_path.start_with?(asset_path+File::SEPARATOR)
return absolute_path[(asset_path+File::SEPARATOR).size..-1]
end
end
nil
end
def glob(type, glob_expression, options = {})
match_all = options.fetch(:match_all, false)
resolved_files = []
@asset_collections.each do |ac|
_, some_resolved_files = ac.globs?(type, glob_expression)
resolved_files += some_resolved_files if some_resolved_files
return resolved_files if resolved_files.any? and !match_all
end
resolved_files
end
protected
def clean_path(relative_path)
# If the image has an target reference, remove it (Common with SVG)
clean_relative_path, target = relative_path.split("#", 2)
# If the image has a query, remove it
clean_relative_path, query = clean_relative_path.split("?", 2)
# Get rid of silliness in the url
clean_relative_path = expand_url_path(clean_relative_path)
[clean_relative_path, query, target]
end
def find_collection(type, relative_path)
asset_collection = nil
clean_relative_path = nil
absolute_path = nil
@asset_collections.each do |ac|
cp, ap = ac.send(:"includes_#{type}?", relative_path)
if ap
asset_collection = ac
absolute_path = ap
clean_relative_path = cp
break
end
end
[asset_collection, clean_relative_path, absolute_path]
end
def absolute_path?(path)
path[0..0] == "/" || path[0..3] == "http"
end
def cache_buster(asset_collection, path, real_path)
cache_buster = compute_cache_buster(asset_collection, path, real_path)
return [path, nil] if cache_buster.nil?
cache_buster = {:query => cache_buster} if cache_buster.is_a?(String)
[cache_buster[:path] || path, cache_buster[:query]]
end
def compute_cache_buster(asset_collection, path, real_path)
file = nil
if asset_collection.asset_cache_buster == :none
nil
elsif asset_collection.asset_cache_buster
args = [path]
if asset_collection.asset_cache_buster.arity > 1
file = File.new(real_path)
args << file
end
asset_collection.asset_cache_buster.call(*args)
else
default_cache_buster(path, real_path)
end
ensure
file.close if file
end
def default_cache_buster(path, real_path)
if File.readable?(real_path)
File.mtime(real_path).to_i.to_s
else
Compass::Util.compass_warn("WARNING: '#{real_path}' cannot be read.")
nil
end
end
end