Class: Condenser::Asset

Inherits:
Object
  • Object
show all
Includes:
ActiveSupport::Benchmarkable, EncodingUtils
Defined in:
lib/condenser/asset.rb

Constant Summary

Constants included from EncodingUtils

EncodingUtils::BOM, EncodingUtils::CHARSET_SIZE, EncodingUtils::CHARSET_START

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from EncodingUtils

#detect, #detect_css, #detect_html, #detect_unicode, #detect_unicode_bom, #scan_css_charset

Constructor Details

#initialize(env, attributes = {}) ⇒ Asset



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/condenser/asset.rb', line 19

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
  @dependencies   = Set.new
  @default_export = nil
  @exports        = nil
  @processed      = false
  
  @processors_loaded = false
  @processors        = Set.new
end

Instance Attribute Details

#content_typesObject (readonly)

Returns the value of attribute content_types.



13
14
15
# File 'lib/condenser/asset.rb', line 13

def content_types
  @content_types
end

#content_types_digestObject (readonly)

Returns the value of attribute content_types_digest.



14
15
16
# File 'lib/condenser/asset.rb', line 14

def content_types_digest
  @content_types_digest
end

#environmentObject (readonly)

Returns the value of attribute environment.



13
14
15
# File 'lib/condenser/asset.rb', line 13

def environment
  @environment
end

#filenameObject (readonly)

Returns the value of attribute filename.



13
14
15
# File 'lib/condenser/asset.rb', line 13

def filename
  @filename
end

#importsObject

Returns the value of attribute imports.



17
18
19
# File 'lib/condenser/asset.rb', line 17

def imports
  @imports
end

#linked_assetsObject (readonly)

Returns the value of attribute linked_assets.



14
15
16
# File 'lib/condenser/asset.rb', line 14

def linked_assets
  @linked_assets
end

#sourceObject



295
296
297
298
# File 'lib/condenser/asset.rb', line 295

def source
  process
  @source
end

#source_fileObject (readonly)

Returns the value of attribute source_file.



13
14
15
# File 'lib/condenser/asset.rb', line 13

def source_file
  @source_file
end

#source_pathObject (readonly)

Returns the value of attribute source_path.



13
14
15
# File 'lib/condenser/asset.rb', line 13

def source_path
  @source_path
end

#sourcemapObject



300
301
302
303
# File 'lib/condenser/asset.rb', line 300

def sourcemap
  process
  @sourcemap
end

Instance Method Details

#all_dependenies(deps, visited, &block) ⇒ Object



105
106
107
108
109
110
111
112
113
# File 'lib/condenser/asset.rb', line 105

def all_dependenies(deps, visited, &block)
  deps.each do |dep|
    if !visited.include?(dep.source_file)
      visited << dep.source_file
      block.call(dep)
      all_dependenies(dep.dependencies, visited, &block)
    end
  end
end

#basepathObject



47
48
49
50
# File 'lib/condenser/asset.rb', line 47

def basepath
  dirname, basename, extensions, mime_types = @environment.decompose_path(filename)
  [dirname, basename].compact.join('/')
end

#cache_key(include_dependencies = true) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/condenser/asset.rb', line 115

def cache_key(include_dependencies=true)
  key = []
  key << [Condenser::VERSION, @source_file, @content_types_digest, stat.ino, stat.mtime.to_f, stat.size]

  if include_dependencies
    all_dependenies(dependencies, []) do |dep|
      key << [dep.source_file, dep.content_types_digest, dep.stat.ino, dep.stat.mtime.to_f, dep.stat.size]
    end
  end

  Digest::SHA1.base64digest(JSON.generate(key))
end

#charsetObject



316
317
318
# File 'lib/condenser/asset.rb', line 316

def charset
  @source.encoding.name.downcase
end

#content_typeObject



43
44
45
# File 'lib/condenser/asset.rb', line 43

def content_type
  @content_types.last
end

#dependenciesObject



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/condenser/asset.rb', line 63

def dependencies
  deps = @environment.cache.fetch("dependencies/#{cache_key(false)}") do
    process
    @dependencies
  end
  
  d = []
  deps.each do |i|
    i = [i, nil] 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

#digestObject



311
312
313
314
# File 'lib/condenser/asset.rb', line 311

def digest
  process
  @digest
end

#eql?(other) ⇒ Boolean Also known as: ==

Public: Compare assets.

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

Returns true or false.



352
353
354
# File 'lib/condenser/asset.rb', line 352

def eql?(other)
  self.class == other.class && self.filename == other.filename && self.content_types == other.content_types
end

#exportObject



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/condenser/asset.rb', line 250

def export
  benchmark "Exporting Asset #{self.filename}", level: :info do
  @environment.build do
    result = @environment.cache.fetch("export/#{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: [],
        dependencies: []
      }
      
      if exporter = @environment.exporters[content_type]
        benchmark "Preprocessing #{data[:filename]} with #{exporter.name}", level: :info do
          exporter.call(@environment, data)
        end
      end

      if minifier = @environment.minifier_for(content_type)
        benchmark "Preprocessing #{data[:filename]} with #{minifier.name}", level: :info do
          minifier.call(@environment, data)
        end
      end
      
      data[:digest] = @environment.digestor.digest(data[:source])
      data[:digest_name] = @environment.digestor.name.sub(/^.*::/, '').downcase
      data
    end
  
    Export.new(@environment, result)
  end
  end
end

#extObject



343
344
345
# File 'lib/condenser/asset.rb', line 343

def ext
  File.extname(filename)
end

#has_default_export?Boolean



79
80
81
82
83
84
# File 'lib/condenser/asset.rb', line 79

def has_default_export?
  @environment.cache.fetch("has_default_export/#{cache_key(false)}") do
    process
    @default_export
  end
end

#has_exports?Boolean



86
87
88
89
90
91
# File 'lib/condenser/asset.rb', line 86

def has_exports?
  @environment.cache.fetch("has_exports/#{cache_key(false)}") do
    process
    @exports
  end
end

#hexdigestObject Also known as: etag

Public: Returns String hexdigest of source.



321
322
323
324
# File 'lib/condenser/asset.rb', line 321

def hexdigest
  process
  @digest.unpack('H*'.freeze).first
end

#inspectObject



56
57
58
59
60
61
# File 'lib/condenser/asset.rb', line 56

def inspect
  dirname, basename, extensions, mime_types = @environment.decompose_path(@filename)
  "    #<\#{self.class.name} @filename=\#{@filename} @content_types=\#{@content_types.inspect} @source_file=\#{@source_file} @source_mime_types=\#{mime_types.inspect}>\n  TEXT\nend\n"

#integrityObject



327
328
329
330
# File 'lib/condenser/asset.rb', line 327

def integrity
  process
  "#{@digest_name}-#{[@digest].pack('m0')}"
end

#lengthObject Also known as: size



305
306
307
308
# File 'lib/condenser/asset.rb', line 305

def length
  process
  @source.bytesize
end

#load_processorsObject



93
94
95
96
97
98
99
100
101
102
103
# File 'lib/condenser/asset.rb', line 93

def load_processors
  return if @processors_loaded

  @processors_loaded = true
  processors = @environment.cache.fetch("processors/#{cache_key(false)}") do
    process
    @processors
  end
  processors.map! { |p| p.is_a?(String) ? p.constantize : p }
  @environment.load_processors(*processors)
end

#loggerObject



128
129
130
# File 'lib/condenser/asset.rb', line 128

def logger
  @environment.logger
end

#pathObject



39
40
41
# File 'lib/condenser/asset.rb', line 39

def path
  filename.sub(/\.(\w+)$/) { |ext| "-#{etag}#{ext}" }
end

#processObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/condenser/asset.rb', line 132

def process
  return if @processed
  
  benchmark "Processing Asset #{self.filename}", level: :info do
  result = @environment.cache.fetch_if(Proc.new { "process/#{cache_key}" }, "dependencies/#{cache_key(false)}") do
    @environment.build 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: [],
        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)
        
        benchmark "Preprocessing #{data[:filename]} with #{templator.name}", level: :info do
          templator.call(@environment, data)
        end
        
        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)

          benchmark "Preprocessing #{self.filename} with #{processor.name}", level: :info do
            processor.call(@environment, data)
          end
        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 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]
      @dependencies = data[: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]
  @dependencies = result[:dependencies]
  @default_export = result[:default_export]
  @exports = result[:exports]
  @processors = result[:processors]
  load_processors

  @processed = true
  end
end

#statObject



52
53
54
# File 'lib/condenser/asset.rb', line 52

def stat
  @stat ||= File.stat(@source_file)
end

#to_jsonObject



332
333
334
# File 'lib/condenser/asset.rb', line 332

def to_json
  { path: path, digest: hexdigest, size: size, integrity: integrity }
end

#to_sObject



290
291
292
293
# File 'lib/condenser/asset.rb', line 290

def to_s
  process
  @source
end

#write(output_directory) ⇒ Object



336
337
338
339
340
341
# File 'lib/condenser/asset.rb', line 336

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