class Sprockets::Manifest

‘#assets` and `#files` for more infomation about the structure.
languages and processes that don’t have sprockets loaded. See
stable. This should make it easy to read from other programming
The JSON is part of the public API and should be considered
indicates with fingerprinted asset is the current one.
lookup without having to compile. A pointer from each logical path
directory. It records basic attributes about the asset for fast
The Manifest logs the contents of assets compiled to a single

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 backups_for(logical_path)

assets mtime in descending order (Newest to oldest).
version is always excluded. The return array is sorted by the
Finds all the backup assets for a logical path. The latest
def backups_for(logical_path)
  files.select { |filename, attrs|
    # Matching logical paths
    attrs['logical_path'] == logical_path &&
      # Excluding whatever asset is the current
      assets[logical_path] != filename
  }.sort_by { |filename, attrs|
    # Sort by timestamp
    Time.parse(attrs['mtime'])
  }.reverse
end

def benchmark

def benchmark
  start_time = Time.now.to_f
  yield
  ((Time.now.to_f - start_time) * 1000).to_i
end

def clean(keep = 2)

keep the latest version plus 2 backups.
Cleanup old assets in the compile directory. By default it will
def clean(keep = 2)
  self.assets.keys.each do |logical_path|
    # Get assets sorted by ctime, newest first
    assets = backups_for(logical_path)
    # Keep the last N backups
    assets = assets[keep..-1] || []
    # Remove old assets
    assets.each { |path, _| remove(path) }
  end
end

def clobber

Wipe directive
def clobber
  FileUtils.rm_r(@dir) if File.exist?(@dir)
  logger.info "Removed #{@dir}"
  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
  paths = environment.each_logical_path(*args).to_a +
    args.flatten.select { |fn| Pathname.new(fn).absolute? if fn.is_a?(String)}
  paths.each do |path|
    if asset = find_asset(path)
      files[asset.digest_path] = {
        'logical_path' => asset.logical_path,
        'mtime'        => asset.mtime.iso8601,
        'size'         => asset.bytesize,
        'digest'       => asset.digest
      }
      assets[asset.logical_path] = asset.digest_path
      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
        asset.write_to "#{target}.gz" if asset.is_a?(BundledAsset)
      end
      save
      asset
    end
  end
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 find_asset(logical_path)

Basic wrapper around Environment#find_asset. Logs compile time.
def find_asset(logical_path)
  asset = nil
  ms = benchmark do
    asset = environment.find_asset(logical_path)
  end
  logger.debug "Compiled #{logical_path}  (#{ms}ms)"
  asset
end

def initialize(*args)


Manifest.new(environment, "./public/assets/manifest.json")

directory.
filename will default a random "manifest-123.json" file in that
compiled assets to. Otherwise, if the path is a directory, the
already exist. The dirname of the `path` will be used to write
a full path to the manifest json file. The file may or may not
Create new Manifest associated with an `environment`. `path` is
def initialize(*args)
  if args.first.is_a?(Base) || args.first.nil?
    @environment = args.shift
  end
  @dir, @path = args[0], args[1]
  # Expand paths
  @dir  = File.expand_path(@dir) if @dir
  @path = File.expand_path(@path) if @path
  # If path is given as the second arg
  if @dir && File.extname(@dir) != ""
    @dir, @path = nil, @dir
  end
  # Default dir to the directory of the path
  @dir ||= File.dirname(@path) if @path
  # If directory is given w/o path, pick a random manifest.json location
  if @dir && @path.nil?
    # Find the first manifest.json in the directory
    paths = Dir[File.join(@dir, "manifest*.json")]
    if paths.any?
      @path = paths.first
    else
      @path = File.join(@dir, "manifest-#{SecureRandom.hex(16)}.json")
    end
  end
  unless @dir && @path
    raise ArgumentError, "manifest requires output path"
  end
  data = nil
  begin
    if File.exist?(@path)
      data = json_decode(File.read(@path))
    end
  rescue MultiJson::DecodeError => e
    logger.error "#{@path} is invalid: #{e.class} #{e.message}"
  end
  @data = data.is_a?(Hash) ? data : {}
end

def json_decode(obj)

def json_decode(obj)
  MultiJson.load(obj)
end

def json_decode(obj)

def json_decode(obj)
  MultiJson.decode(obj)
end

def json_encode(obj)

def json_encode(obj)
  MultiJson.dump(obj)
end

def json_encode(obj)

def json_encode(obj)
  MultiJson.encode(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)
  gzip = "#{path}.gz"
  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)
  FileUtils.rm(gzip) if File.exist?(gzip)
  save
  logger.info "Removed #{filename}"
  nil
end

def save

Persist manfiest back to FS
def save
  FileUtils.mkdir_p dir
  File.open(path, 'w') do |f|
    f.write json_encode(@data)
  end
end