module Sprockets
module Resolve
# Public: Iterate over every file under all load paths.
#
# Returns Enumerator if no block is given.
def each_file
return to_enum(__method__) unless block_given?
paths.each do |root|
stat_tree(root).each do |filename, stat|
if stat.file?
yield filename
end
end
end
nil
end
# Finds the expanded real path for a given logical path by
# searching the environment's paths.
#
# resolve("application.js")
# # => "/path/to/app/javascripts/application.js.coffee"
#
# A `FileNotFound` exception is raised if the file does not exist.
def resolve(path, options = {})
resolve_all(path, options) do |filename|
return filename
end
accept = options[:accept]
message = "couldn't find file '#{path}'"
message << " with type '#{accept}'" if accept
raise FileNotFound, message
end
def resolve_in_load_path(load_path, logical_path, options = {})
if !self.paths.include?(load_path.to_s)
raise FileOutsidePaths, "#{load_path} isn't in paths: #{self.paths.join(', ')}"
end
resolve_all_under_load_path(load_path, logical_path, options) do |filename|
return filename
end
accept = options[:accept]
message = "couldn't find file '#{logical_path}' under '#{load_path}'"
message << " with type '#{accept}'" if accept
raise FileNotFound, message
end
def resolve_all_under_load_path(load_path, logical_path, options = {}, &block)
return to_enum(__method__, load_path, logical_path, options) unless block_given?
logical_name, mime_type, _ = parse_path_extnames(logical_path)
logical_basename = File.basename(logical_name)
accepts = parse_accept_options(mime_type, options[:accept])
_resolve_all_under_load_path(load_path, logical_name, logical_basename, accepts, &block)
nil
end
# Public: Finds the expanded real path for a given logical path by searching
# the environment's paths. Includes all matching paths including fallbacks
# and shadowed matches.
#
# resolve_all("application.js").first
# # => "/path/to/app/javascripts/application.js.coffee"
#
# `resolve_all` returns an `Enumerator`. This allows you to filter your
# matches by any condition.
#
# resolve_all("application").find do |path|
# mime_type_for(path) == "text/css"
# end
#
def resolve_all(path, options = {}, &block)
return to_enum(__method__, path, options) unless block_given?
path = path.to_s
logical_name, mime_type, _ = parse_path_extnames(path)
logical_basename = File.basename(logical_name)
accepts = parse_accept_options(mime_type, options[:accept])
self.paths.each do |load_path|
_resolve_all_under_load_path(load_path, logical_name, logical_basename, accepts, &block)
end
nil
end
# Experimental: Get transform type for filename
def resolve_path_transform_type(filename, accept)
mime_type = parse_path_extnames(filename)[1]
resolve_transform_type(mime_type, accept)
end
# Experimental
def resolve_asset_uri(path, options = {})
path = path.to_s
accept = options[:accept]
skip_bundle = options.key?(:bundle) ? !options[:bundle] : false
available_encodings = self.encodings.keys + ['identity']
encoding = find_best_q_match(options[:accept_encoding], available_encodings)
if absolute_path?(path)
path = File.expand_path(path)
if file?(path) && (accept.nil? || resolve_path_transform_type(path, accept))
filename = path
type = resolve_path_transform_type(path, accept)
end
else
if filename = resolve_all(path, accept: accept).first
mime_type = parse_path_extnames(path)[1]
accept = parse_accept_options(mime_type, accept).map { |t, v| "#{t}; q=#{v}" }.join(", ")
type = resolve_path_transform_type(filename, accept)
end
end
if filename
encoding = nil if encoding == 'identity'
AssetURI.build(filename, type: type, skip_bundle: skip_bundle, encoding: encoding)
end
end
# Public: Enumerate over all logical paths in the environment.
#
# Returns an Enumerator of [logical_path, filename].
def logical_paths
return to_enum(__method__) unless block_given?
seen = Set.new
self.paths.each do |load_path|
stat_tree(load_path).each do |filename, stat|
next unless stat.file?
path = split_subpath(load_path, filename)
path, mime_type, _ = parse_path_extnames(path)
path = normalize_logical_path(path)
path += mime_types[mime_type][:extensions].first if mime_type
if !seen.include?(path)
yield path, filename
seen << path
end
end
end
nil
end
# Deprecated: Iterate over all logical paths with a matcher.
#
# Remove from 4.x.
#
# args - List of matcher objects.
#
# Returns Enumerator if no block is given.
def each_logical_path(*args, &block)
return to_enum(__method__, *args) unless block_given?
filters = args.flatten.map { |arg| Manifest.compile_match_filter(arg) }
logical_paths.each do |a, b|
if filters.any? { |f| f.call(a, b) }
if block.arity == 2
yield a, b
else
yield a
end
end
end
nil
end
protected
def parse_accept_options(mime_type, types)
accepts = []
accepts += parse_q_values(types) if types
if mime_type
if accepts.empty? || accepts.any? { |accept, _| match_mime_type?(mime_type, accept) }
accepts = [[mime_type, 1.0]]
else
return []
end
end
if accepts.empty?
accepts << ['*/*', 1.0]
end
accepts
end
def normalize_logical_path(path)
dirname, basename = File.split(path)
path = dirname if basename == 'index'
path
end
def _resolve_all_under_load_path(load_path, logical_name, logical_basename, accepts, &block)
filenames = path_matches(load_path, logical_name, logical_basename)
matches = []
# TODO: Cleanup double iteration of accept and filenames
# Exact mime type match first
matches += find_q_matches(accepts, filenames) do |filename, accepted|
if !file?(filename)
nil
elsif accepted == '*/*'
filename
elsif parse_path_extnames(filename)[1] == accepted
filename
end
end
# Then transformable match
matches += find_q_matches(accepts, filenames) do |filename, accepted|
if !file?(filename)
nil
elsif accepted == '*/*'
filename
elsif resolve_path_transform_type(filename, accepted)
filename
end
end
matches.uniq.each(&block)
end
def path_matches(load_path, logical_name, logical_basename)
filenames = []
dirname = File.dirname(File.join(load_path, logical_name))
dirname_matches(dirname, logical_basename) { |fn| filenames << fn }
resolve_alternates(load_path, logical_name) { |fn| filenames << fn }
dirname_matches(File.join(load_path, logical_name), "index") { |fn| filenames << fn }
filenames
end
def dirname_matches(dirname, basename)
self.entries(dirname).each do |entry|
name = parse_path_extnames(entry)[0]
if basename == name
yield File.join(dirname, entry)
end
end
end
def resolve_alternates(load_path, logical_name)
end
# Internal: Returns the name, mime type and `Array` of engine extensions.
#
# "foo.js.coffee.erb"
# # => ["foo", "application/javascript", [".coffee", ".erb"]]
#
def parse_path_extnames(path)
mime_type = nil
engine_extnames = []
len = path.length
path_extnames(path).reverse_each do |extname|
if engines.key?(extname)
mime_type = engine_mime_types[extname]
engine_extnames.unshift(extname)
len -= extname.length
elsif mime_exts.key?(extname)
mime_type = mime_exts[extname]
len -= extname.length
break
else
break
end
end
name = path[0, len]
return [name, mime_type, engine_extnames]
end
end
end