class Sprockets::Manifest
def self.compile_match_filter(filter)
compile_match_filter("foo/*.js")
compile_match_filter(/application.js/)
})
File.extname(logical_path) == '.js'
compile_match_filter(proc { |logical_path|
passed to logical_paths.select(&proc).
Deprecated: Compile logical path matching filter into a proc that can be
def self.compile_match_filter(filter) # If the filter is already a proc, great nothing to do. if filter.respond_to?(:call) filter # If the filter is a regexp, wrap it in a proc that tests it against the # logical path. elsif filter.is_a?(Regexp) proc { |logical_path| filter.match(logical_path) } elsif filter.is_a?(String) # If its an absolute path, detect the matching full filename if PathUtils.absolute_path?(filter) proc { |logical_path, filename| filename == filter.to_s } else # Otherwise do an fnmatch against the logical path. proc { |logical_path| File.fnmatch(filter.to_s, logical_path) } end else raise TypeError, "unknown filter type: #{filter.inspect}" end end
def self.compute_alias_logical_path(path)
def self.compute_alias_logical_path(path) dirname, basename = File.split(path) extname = File.extname(basename) if File.basename(basename, extname) == 'index' "#{dirname}#{extname}" else nil end end
def self.simple_logical_path?(str)
def self.simple_logical_path?(str) str.is_a?(String) && !PathUtils.absolute_path?(str) && str !~ /\*|\*\*|\?|\[|\]|\{|\}/ end
def assets
"jquery.js" => "jquery-ae0908555a245f8266f77df5a8edca2e.js" }
{ "application.js" => "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js",
Logical path (String): Fingerprint path (String)
map to the latest fingerprinted filename.
Returns internal assets mapping. Keys are logical paths which
def assets @data['assets'] ||= {} end
def clean(count = 2, age = 3600)
age=600.
To only keep files created within the last 10 minutes, set count=0 and
To force only 1 backup to be kept, set count=1 and age=0.
Examples
keep the latest version, 2 backups and any created within the past hour.
Cleanup old assets in the compile directory. By default it will
def clean(count = 2, age = 3600) asset_versions = files.group_by { |_, attrs| attrs['logical_path'] } asset_versions.each do |logical_path, versions| current = assets[logical_path] versions.reject { |path, _| path == current }.sort_by { |_, attrs| # Sort by timestamp Time.parse(attrs['mtime']) }.reverse.each_with_index.drop_while { |(_, attrs), index| _age = [0, Time.now - Time.parse(attrs['mtime'])].max # Keep if under age or within the count limit _age < age || index < count }.each { |(path, _), _| # Remove old assets remove(path) } end end
def clobber
def clobber FileUtils.rm_r(directory) if File.exist?(directory) logger.info "Removed #{directory}" nil end
def compile(*args)
compile("application.js")
also inserted into the manifest file.
`application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
fingerprinted filename like
Compile and write asset to directory. The asset is written to a
def compile(*args) unless environment raise Error, "manifest requires environment for compilation" end filenames = [] find(*args) do |asset| files[asset.digest_path] = { 'logical_path' => asset.logical_path, 'mtime' => asset.mtime.iso8601, 'size' => asset.bytesize, 'digest' => asset.hexdigest, # Deprecated: Remove beta integrity attribute in next release. # Callers should DigestUtils.hexdigest_integrity_uri to compute the # digest themselves. 'integrity' => DigestUtils.hexdigest_integrity_uri(asset.hexdigest) } assets[asset.logical_path] = asset.digest_path if alias_logical_path = self.class.compute_alias_logical_path(asset.logical_path) assets[alias_logical_path] = asset.digest_path end target = File.join(dir, asset.digest_path) if File.exist?(target) logger.debug "Skipping #{target}, already exists" else logger.info "Writing #{target}" asset.write_to target end filenames << asset.filename end save filenames end
def files
'digest' => "2e8e9a7c6b0aafa0c9bdeec90ea30213" } }
'mtime' => "2011-12-13T21:47:08-06:00",
{ 'logical_path' => "application.js",
{ "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js" =>
digest: Base64 hex digest (String)
mtime: ISO8601 mtime (String)
logical_path: Logical path (String)
Fingerprint path (String):
which map to an attributes array.
Returns internal file directory listing. Keys are filenames
def files @data['files'] ||= {} end
def filter_logical_paths(*args)
files you want to compile.
Deprecated: Filter logical paths in environment. Useful for selecting what
def filter_logical_paths(*args) filters = args.flatten.map { |arg| self.class.compile_match_filter(arg) } environment.cached.logical_paths.select do |a, b| filters.any? { |f| f.call(a, b) } end end
def find(*args)
Public: Find all assets matching pattern set in environment.
def find(*args) unless environment raise Error, "manifest requires environment for compilation" end return to_enum(__method__, *args) unless block_given? paths, filters = args.flatten.partition { |arg| self.class.simple_logical_path?(arg) } filters = filters.map { |arg| self.class.compile_match_filter(arg) } environment = self.environment.cached paths.each do |path| environment.find_all_linked_assets(path) do |asset| yield asset end end if filters.any? environment.logical_paths do |logical_path, filename| if filters.any? { |f| f.call(logical_path, filename) } environment.find_all_linked_assets(filename) do |asset| yield asset end end end end nil end
def initialize(*args)
Manifest.new(environment, "./public/assets/manifest.json")
".sprockets-manifest-*.json" file in that directory.
Otherwise, if the path is a directory, the filename will default a random
dirname of the `filename` will be used to write compiled assets to.
path to the manifest json file. The file may or may not already exist. The
Create new Manifest associated with an `environment`. `filename` is a full
def initialize(*args) if args.first.is_a?(Base) || args.first.nil? @environment = args.shift end @directory, @filename = args[0], args[1] # Whether the manifest file is using the old manifest-*.json naming convention @legacy_manifest = false # Expand paths @directory = File.expand_path(@directory) if @directory @filename = File.expand_path(@filename) if @filename # If filename is given as the second arg if @directory && File.extname(@directory) != "" @directory, @filename = nil, @directory end # Default dir to the directory of the filename @directory ||= File.dirname(@filename) if @filename # If directory is given w/o filename, pick a random manifest location @rename_filename = nil if @directory && @filename.nil? @filename = find_directory_manifest(@directory) # If legacy manifest name autodetected, mark to rename on save if File.basename(@filename).start_with?("manifest") @rename_filename = File.join(@directory, generate_manifest_path) end end unless @directory && @filename raise ArgumentError, "manifest requires output filename" end data = {} begin if File.exist?(@filename) data = json_decode(File.read(@filename)) end rescue JSON::ParserError => e logger.error "#{@filename} is invalid: #{e.class} #{e.message}" end @data = data end
def json_decode(obj)
def json_decode(obj) JSON.parse(obj, create_additions: false) end
def json_encode(obj)
def json_encode(obj) JSON.generate(obj) end
def logger
def logger if environment environment.logger else logger = Logger.new($stderr) logger.level = Logger::FATAL logger end end
def remove(filename)
manifest.remove("application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js")
be the name with any directory path.
Removes file from directory and from manifest. `filename` must
def remove(filename) path = File.join(dir, filename) logical_path = files[filename]['logical_path'] if assets[logical_path] == filename assets.delete(logical_path) end files.delete(filename) FileUtils.rm(path) if File.exist?(path) save logger.info "Removed #{filename}" nil end
def save
def save if @rename_filename logger.info "Renaming #{@filename} to #{@rename_filename}" FileUtils.mv(@filename, @rename_filename) @filename = @rename_filename @rename_filename = nil end data = json_encode(@data) FileUtils.mkdir_p File.dirname(@filename) PathUtils.atomic_write(@filename) do |f| f.write(data) end end