class Condenser::Asset

Attributes

content_types[R]
content_types_digest[R]
environment[R]
exports[R]
filename[R]
imports[RW]
linked_assets[R]
processed[RW]
source[W]
source_file[R]
source_path[R]
sourcemap[W]

Public Class Methods

new(env, attributes={}) click to toggle source
# File lib/condenser/asset.rb, line 18
def initialize(env, attributes={})
  @environment    = env
  
  @filename       = attributes[:filename]
  @content_types  = Array(attributes[:content_types] || attributes[:content_type])
  @content_types_digest = Digest::SHA1.base64digest(@content_types.join(':'))

  @source_file    = attributes[:source_file]
  @source_path    = attributes[:source_path]
  
  @linked_assets        = Set.new
  @process_dependencies = Set.new
  @export_dependencies  = Set.new
  @default_export       = nil
  @exports              = nil
  @processed            = false
  @pcv                  = nil
  @export               = nil
  @ecv                  = nil
  @processors_loaded    = false
  @processors           = Set.new
end

Public Instance Methods

==(other)
Alias for: eql?
all_dependenies(deps, visited, meth, &block) click to toggle source
# File lib/condenser/asset.rb, line 116
def all_dependenies(deps, visited, meth, &block)
  deps.each do |dep|
    if !visited.include?(dep.source_file)
      visited << dep.source_file
      block.call(dep)
      all_dependenies(dep.send(meth), visited, meth, &block)
    end
  end
end
all_export_dependencies() click to toggle source
# File lib/condenser/asset.rb, line 134
def all_export_dependencies
  f = [@source_file]
  all_dependenies(export_dependencies, [], :export_dependencies) do |dep|
    f << dep.source_file
  end
  f
end
all_process_dependencies() click to toggle source
# File lib/condenser/asset.rb, line 126
def all_process_dependencies
  f = [@source_file]
  all_dependenies(process_dependencies, [], :process_dependencies) do |dep|
    f << dep.source_file
  end
  f
end
basepath() click to toggle source
# File lib/condenser/asset.rb, line 49
def basepath
  dirname, basename, extensions, mime_types = @environment.decompose_path(filename)
  [dirname, basename].compact.join('/')
end
cache_key() click to toggle source
# File lib/condenser/asset.rb, line 142
def cache_key
  @cache_key ||= Digest::SHA1.base64digest(JSON.generate([
    Condenser::VERSION,
    @environment.pipline_digest,
    @environment.base ? @source_file.delete_prefix(@environment.base) : @source_file,
    Digest::SHA256.file(@source_file).hexdigest,
    @content_types_digest
  ]))
end
charset() click to toggle source
# File lib/condenser/asset.rb, line 389
def charset
  @source.encoding.name.downcase
end
content_type() click to toggle source
# File lib/condenser/asset.rb, line 45
def content_type
  @content_types.last
end
digest() click to toggle source
# File lib/condenser/asset.rb, line 384
def digest
  process
  @digest
end
eql?(other) click to toggle source

Public: Compare assets.

Assets are equal if they share the same path and digest.

Returns true or false.

# File lib/condenser/asset.rb, line 425
def eql?(other)
  self.class == other.class && self.filename == other.filename && self.content_types == other.content_types
end
Also aliased as: ==
etag()
Alias for: hexdigest
export() click to toggle source
# File lib/condenser/asset.rb, line 318
def export
  return @export if @export
  
  @export = @environment.build do
    data = @environment.cache.fetch_if(Proc.new {"export/#{cache_key}/#{export_cache_version}"}, "export-deps/#{cache_key}") do
      process
      dirname, basename, extensions, mime_types = @environment.decompose_path(@filename)
      data = {
        source: @source.dup,
        source_file: @source_file,
    
        filename: @filename.dup,
        content_types: @content_types,

        sourcemap: nil,
        linked_assets: [],
        process_dependencies: [],
        export_dependencies: []
      }
    
      if exporter = @environment.exporters[content_type]
        exporter.call(@environment, data)
      end

      if minifier = @environment.minifier_for(content_type)
        minifier.call(@environment, data)
      end
    
      data[:digest] = @environment.digestor.digest(data[:source])
      data[:digest_name] = @environment.digestor.name.sub(/^.*::/, '').downcase
      data
    end

    if @environment.build_cache.listening
      # TODO we could skip file and all their depencies here if they are
      # already in build_cache.@export_dependencies
      all_export_dependencies.each do |sf|
        @environment.build_cache.instance_variable_get(:@export_dependencies)[sf]&.add(self)
      end
    end

    Export.new(@environment, data)
  end
end
export_cache_version() click to toggle source
# File lib/condenser/asset.rb, line 166
def export_cache_version
  return @ecv if @ecv

  f = []
  all_dependenies(export_dependencies, [], :export_dependencies) do |dep|
    f << [
      @environment.base ? dep.source_file.delete_prefix(@environment.base) : dep.source_file,
      Digest::SHA256.file(dep.source_file).hexdigest
    ]
  end

  @ecv = Digest::SHA1.base64digest(JSON.generate(f))
end
export_dependencies() click to toggle source
# File lib/condenser/asset.rb, line 81
def export_dependencies
  deps = @environment.cache.fetch "export-deps/#{cache_key}" do
    process
    @export_dependencies + @process_dependencies
  end
  
  d = []
  deps.each do |i|
    i = [i, @content_types] if i.is_a?(String)
    @environment.resolve(i[0], File.dirname(@source_file), accept: i[1]).each do |asset|
      d << asset
    end
  end
  d
end
ext() click to toggle source
# File lib/condenser/asset.rb, line 416
def ext
  File.extname(filename)
end
has_default_export?() click to toggle source
# File lib/condenser/asset.rb, line 97
def has_default_export?
  process
  @default_export
end
has_exports?() click to toggle source
# File lib/condenser/asset.rb, line 102
def has_exports?
  process
  @exports
end
hexdigest() click to toggle source

Public: Returns String hexdigest of source.

# File lib/condenser/asset.rb, line 394
def hexdigest
  process
  @digest.unpack('H*'.freeze).first
end
Also aliased as: etag
inspect() click to toggle source
# File lib/condenser/asset.rb, line 58
    def inspect
      dirname, basename, extensions, mime_types = @environment.decompose_path(@filename)
      <<-TEXT
        #<#{self.class.name}##{self.object_id} @filename=#{@filename} @content_types=#{@content_types.inspect} @source_file=#{@source_file} @source_mime_types=#{mime_types.inspect}>
      TEXT
    end
integrity() click to toggle source
# File lib/condenser/asset.rb, line 400
def integrity
  process
  "#{@digest_name}-#{[@digest].pack('m0')}"
end
length() click to toggle source
# File lib/condenser/asset.rb, line 378
def length
  process
  @source.bytesize
end
Also aliased as: size
load_processors() click to toggle source
# File lib/condenser/asset.rb, line 107
def load_processors
  return if @processors_loaded

  @processors_loaded = true
  process
  @processors.map! { |p| p.is_a?(String) ? p.constantize : p }
  @environment.load_processors(*@processors)
end
needs_reexporting!() click to toggle source
# File lib/condenser/asset.rb, line 187
def needs_reexporting!
  restat!
  @export = nil
  @ecv = nil
end
needs_reprocessing!() click to toggle source
# File lib/condenser/asset.rb, line 180
def needs_reprocessing!
  @processed = false
  @pcv = nil
  @cache_key = nil
  needs_reexporting!
end
path() click to toggle source
# File lib/condenser/asset.rb, line 41
def path
  filename.sub(/\.(\w+)$/) { |ext| "-#{etag}#{ext}" }
end
process() click to toggle source
# File lib/condenser/asset.rb, line 193
def process
  return if @processed
  
  result = @environment.build do
    @environment.cache.fetch_if(Proc.new {"process/#{cache_key}/#{process_cache_version}"}, "direct-deps/#{cache_key}") do
      @source = File.binread(@source_file)
      dirname, basename, extensions, mime_types = @environment.decompose_path(@source_file)
      
      data = {
        source: @source,
        source_file: @source_file,
    
        filename: @filename.dup,
        content_type: mime_types,

        map: nil,
        linked_assets: [],
        process_dependencies: [],
        export_dependencies: [],
        
        processors: Set.new
      }
    
      while @environment.templates.has_key?(data[:content_type].last)
        templator = @environment.templates[data[:content_type].pop]
        
        templator_klass = (templator.is_a?(Class) ? templator : templator.class)
        data[:processors] << templator_klass.name
        @environment.load_processors(templator_klass)
        
        templator.call(@environment, data)
        data[:filename] = data[:filename].gsub(/\.#{extensions.last}$/, '')
      end
      
      case @environment.mime_types[data[:content_type].last][:charset]
      when :unicode
        detect_unicode(data[:source])
      when :css
        detect_css(data[:source])
      when :html
        detect_html(data[:source])
      else
        detect(data[:source]) if mime_types.last.start_with?('text/')
      end
      
      if @environment.preprocessors.has_key?(data[:content_type].last)
        @environment.preprocessors[data[:content_type].last].each do |processor|
          processor_klass = (processor.is_a?(Class) ? processor : processor.class)
          data[:processors] << processor_klass.name
          @environment.load_processors(processor_klass)

          @environment.logger.info { "Pre Processing #{self.filename} with #{processor.name}" }
          processor.call(@environment, data)
        end
      end
  
      if data[:content_type].last != @content_types.last && @environment.transformers.has_key?(data[:content_type].last)
        from_mime_type = data[:content_type].pop
        @environment.transformers[from_mime_type].each do |to_mime_type, processor|
          processor_klass = (processor.is_a?(Class) ? processor : processor.class)
          data[:processors] << processor_klass.name
          @environment.load_processors(processor_klass)
          
          @environment.logger.info { "Transforming #{self.filename} from #{from_mime_type} to #{to_mime_type} with #{processor.name}" }
          processor.call(@environment, data)
          data[:content_type] << to_mime_type
        end
      end
      
      if @environment.postprocessors.has_key?(data[:content_type].last)
        @environment.postprocessors[data[:content_type].last].each do |processor|
          processor_klass = (processor.is_a?(Class) ? processor : processor.class)
          data[:processors] << processor_klass.name
          @environment.load_processors(processor_klass)

          @environment.logger.info { "Post Processing #{self.filename} with #{processor.name}" }
          processor.call(@environment, data)
        end
      end
  
      if mime_types != @content_types
        raise ContentTypeMismatch, "mime type(s) \"#{@content_types.join(', ')}\" does not match requested mime type(s) \"#{data[:mime_types].join(', ')}\""
      end
  
      data[:digest] = @environment.digestor.digest(data[:source])
      data[:digest_name] = @environment.digestor.name.sub(/^.*::/, '').downcase

      # Do this here and at the end so cache_key can be calculated if we
      # run this block
      @source = data[:source]
      @sourcemap = data[:map]
      @filename = data[:filename]
      @content_types = data[:content_type]
      @digest = data[:digest]
      @digest_name = data[:digest_name]
      @linked_assets = data[:linked_assets]
      @process_dependencies = data[:process_dependencies]
      @export_dependencies = data[:export_dependencies]
      @default_export = data[:default_export]
      @exports = data[:exports]
      @processors = data[:processors]
      @processors_loaded = true
      @processed = true
      
      data
    end
  end
  
  @source = result[:source]
  @sourcemap = result[:map]
  @filename = result[:filename]
  @content_types = result[:content_type]
  @digest = result[:digest]
  @digest_name = result[:digest_name]
  @linked_assets = result[:linked_assets]
  @process_dependencies = result[:process_dependencies]
  @export_dependencies  = result[:export_dependencies]
  @default_export = result[:default_export]
  @exports = result[:exports]
  @processors = result[:processors]
  load_processors

  @processed = true
end
process_cache_version() click to toggle source
# File lib/condenser/asset.rb, line 152
def process_cache_version
  return @pcv if @pcv

  f = []
  all_dependenies(process_dependencies, [], :process_dependencies) do |dep|
    f << [
      @environment.base ? dep.source_file.delete_prefix(@environment.base) : dep.source_file,
      Digest::SHA256.file(dep.source_file).hexdigest
    ]
  end
  
  @pcv = Digest::SHA1.base64digest(JSON.generate(f))
end
process_dependencies() click to toggle source
# File lib/condenser/asset.rb, line 65
def process_dependencies
  deps = @environment.cache.fetch "direct-deps/#{cache_key}" do
    process
    @process_dependencies
  end

  d = []
  deps.each do |i|
    i = [i, @content_types] if i.is_a?(String)
    @environment.resolve(i[0], File.dirname(@source_file), accept: i[1]).each do |asset|
      d << asset
    end
  end
  d
end
restat!() click to toggle source
# File lib/condenser/asset.rb, line 54
def restat!
  @stat = nil
end
size()
Alias for: length
source() click to toggle source
# File lib/condenser/asset.rb, line 368
def source
  process
  @source
end
sourcemap() click to toggle source
# File lib/condenser/asset.rb, line 373
def sourcemap
  process
  @sourcemap
end
to_json() click to toggle source
# File lib/condenser/asset.rb, line 405
def to_json
  { path: path, digest: hexdigest, size: size, integrity: integrity }
end
to_s() click to toggle source
# File lib/condenser/asset.rb, line 363
def to_s
  process
  @source
end
write(output_directory) click to toggle source
# File lib/condenser/asset.rb, line 409
def write(output_directory)
  files = @environment.writers_for_mime_type(content_type).map do |writer|
    writer.call(output_directory, self)
  end
  files.flatten.compact
end