sprockets 3.0.1 → 3.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +308 -0
  3. data/README.md +49 -187
  4. data/bin/sprockets +1 -0
  5. data/lib/sprockets/asset.rb +3 -2
  6. data/lib/sprockets/base.rb +13 -2
  7. data/lib/sprockets/bundle.rb +5 -1
  8. data/lib/sprockets/cache/file_store.rb +7 -4
  9. data/lib/sprockets/cache.rb +6 -4
  10. data/lib/sprockets/closure_compressor.rb +5 -11
  11. data/lib/sprockets/coffee_script_processor.rb +2 -2
  12. data/lib/sprockets/coffee_script_template.rb +12 -1
  13. data/lib/sprockets/compressing.rb +20 -0
  14. data/lib/sprockets/dependencies.rb +8 -8
  15. data/lib/sprockets/deprecation.rb +90 -0
  16. data/lib/sprockets/digest_utils.rb +81 -57
  17. data/lib/sprockets/directive_processor.rb +2 -0
  18. data/lib/sprockets/eco_processor.rb +2 -2
  19. data/lib/sprockets/eco_template.rb +12 -1
  20. data/lib/sprockets/ejs_processor.rb +2 -2
  21. data/lib/sprockets/ejs_template.rb +12 -1
  22. data/lib/sprockets/encoding_utils.rb +7 -4
  23. data/lib/sprockets/engines.rb +11 -0
  24. data/lib/sprockets/erb_processor.rb +13 -1
  25. data/lib/sprockets/erb_template.rb +6 -1
  26. data/lib/sprockets/errors.rb +0 -1
  27. data/lib/sprockets/http_utils.rb +3 -1
  28. data/lib/sprockets/legacy.rb +20 -12
  29. data/lib/sprockets/legacy_proc_processor.rb +1 -1
  30. data/lib/sprockets/legacy_tilt_processor.rb +2 -2
  31. data/lib/sprockets/loader.rb +208 -59
  32. data/lib/sprockets/manifest.rb +57 -6
  33. data/lib/sprockets/mime.rb +26 -6
  34. data/lib/sprockets/path_utils.rb +20 -15
  35. data/lib/sprockets/processing.rb +10 -0
  36. data/lib/sprockets/processor_utils.rb +77 -0
  37. data/lib/sprockets/resolve.rb +10 -7
  38. data/lib/sprockets/sass_cache_store.rb +6 -1
  39. data/lib/sprockets/sass_compressor.rb +9 -17
  40. data/lib/sprockets/sass_processor.rb +16 -9
  41. data/lib/sprockets/sass_template.rb +14 -2
  42. data/lib/sprockets/server.rb +34 -14
  43. data/lib/sprockets/uglifier_compressor.rb +6 -13
  44. data/lib/sprockets/unloaded_asset.rb +137 -0
  45. data/lib/sprockets/uri_tar.rb +98 -0
  46. data/lib/sprockets/uri_utils.rb +14 -11
  47. data/lib/sprockets/utils/gzip.rb +67 -0
  48. data/lib/sprockets/utils.rb +36 -18
  49. data/lib/sprockets/version.rb +1 -1
  50. data/lib/sprockets/yui_compressor.rb +4 -14
  51. data/lib/sprockets.rb +21 -11
  52. metadata +49 -11
@@ -22,8 +22,8 @@ module Sprockets
22
22
  data = input[:data]
23
23
  context = input[:environment].context_class.new(input)
24
24
 
25
- data = @klass.new(filename) { data }.render(context)
26
- context.metadata.merge(data: data)
25
+ data = @klass.new(filename) { data }.render(context, {})
26
+ context.metadata.merge(data: data.to_str)
27
27
  end
28
28
  end
29
29
  end
@@ -10,34 +10,54 @@ require 'sprockets/processor_utils'
10
10
  require 'sprockets/resolve'
11
11
  require 'sprockets/transformers'
12
12
  require 'sprockets/uri_utils'
13
+ require 'sprockets/unloaded_asset'
13
14
 
14
15
  module Sprockets
16
+
15
17
  # The loader phase takes a asset URI location and returns a constructed Asset
16
18
  # object.
17
19
  module Loader
18
20
  include DigestUtils, PathUtils, ProcessorUtils, URIUtils
19
21
  include Engines, Mime, Processing, Resolve, Transformers
20
22
 
21
- # Public: Load Asset by AssetURI.
23
+
24
+ # Public: Load Asset by Asset URI.
25
+ #
26
+ # uri - A String containing complete URI to a file including schema
27
+ # and full path such as:
28
+ # "file:///Path/app/assets/js/app.js?type=application/javascript"
22
29
  #
23
- # uri - AssetURI
24
30
  #
25
31
  # Returns Asset.
26
32
  def load(uri)
27
- filename, params = parse_asset_uri(uri)
28
- if params.key?(:id)
29
- asset = cache.fetch("asset-uri:#{VERSION}#{uri}") do
30
- load_asset_by_id_uri(uri, filename, params)
33
+ unloaded = UnloadedAsset.new(uri, self)
34
+ if unloaded.params.key?(:id)
35
+ unless asset = asset_from_cache(unloaded.asset_key)
36
+ id = unloaded.params.delete(:id)
37
+ uri_without_id = build_asset_uri(unloaded.filename, unloaded.params)
38
+ asset = load_from_unloaded(UnloadedAsset.new(uri_without_id, self))
39
+ if asset[:id] != id
40
+ @logger.warn "Sprockets load error: Tried to find #{uri}, but latest was id #{asset[:id]}"
41
+ end
31
42
  end
32
43
  else
33
- asset = fetch_asset_from_dependency_cache(uri, filename) do |paths|
44
+ asset = fetch_asset_from_dependency_cache(unloaded) do |paths|
45
+ # When asset is previously generated, its "dependencies" are stored in the cache.
46
+ # The presence of `paths` indicates dependencies were stored.
47
+ # We can check to see if the dependencies have not changed by "resolving" them and
48
+ # generating a digest key from the resolved entries. If this digest key has not
49
+ # changed the asset will be pulled from cache.
50
+ #
51
+ # If this `paths` is present but the cache returns nothing then `fetch_asset_from_dependency_cache`
52
+ # will confusingly be called again with `paths` set to nil where the asset will be
53
+ # loaded from disk.
34
54
  if paths
35
- digest = digest(resolve_dependencies(paths))
36
- if id_uri = cache.get("asset-uri-digest:#{VERSION}:#{uri}:#{digest}", true)
37
- cache.get("asset-uri:#{VERSION}:#{id_uri}", true)
55
+ digest = DigestUtils.digest(resolve_dependencies(paths))
56
+ if uri_from_cache = cache.get(unloaded.digest_key(digest), true)
57
+ asset_from_cache(UnloadedAsset.new(uri_from_cache, self).asset_key)
38
58
  end
39
59
  else
40
- load_asset_by_uri(uri, filename, params)
60
+ load_from_unloaded(unloaded)
41
61
  end
42
62
  end
43
63
  end
@@ -45,47 +65,58 @@ module Sprockets
45
65
  end
46
66
 
47
67
  private
48
- def load_asset_by_id_uri(uri, filename, params)
49
- # Internal assertion, should be routed through load_asset_by_uri
50
- unless id = params.delete(:id)
51
- raise ArgumentError, "expected uri to have an id: #{uri}"
52
- end
53
68
 
54
- uri = build_asset_uri(filename, params)
55
- asset = load_asset_by_uri(uri, filename, params)
69
+ # Internal: Load asset hash from cache
70
+ #
71
+ # key - A String containing lookup information for an asset
72
+ #
73
+ # This method converts all "compressed" paths to absolute paths.
74
+ # Returns a hash of values representing an asset
75
+ def asset_from_cache(key)
76
+ asset = cache.get(key, true)
77
+ if asset
78
+ asset[:uri] = expand_from_root(asset[:uri])
79
+ asset[:load_path] = expand_from_root(asset[:load_path])
80
+ asset[:filename] = expand_from_root(asset[:filename])
81
+ asset[:metadata][:included].map! { |uri| expand_from_root(uri) } if asset[:metadata][:included]
82
+ asset[:metadata][:links].map! { |uri| expand_from_root(uri) } if asset[:metadata][:links]
83
+ asset[:metadata][:stubbed].map! { |uri| expand_from_root(uri) } if asset[:metadata][:stubbed]
84
+ asset[:metadata][:required].map! { |uri| expand_from_root(uri) } if asset[:metadata][:required]
85
+ asset[:metadata][:dependencies].map! { |uri| uri.start_with?("file-digest://") ? expand_from_root(uri) : uri } if asset[:metadata][:dependencies]
56
86
 
57
- if id && asset[:id] != id
58
- raise VersionNotFound, "could not find specified id: #{uri}##{id}"
87
+ asset[:metadata].each_key do |k|
88
+ next unless k =~ /_dependencies\z/
89
+ asset[:metadata][k].map! { |uri| expand_from_root(uri) }
90
+ end
59
91
  end
60
-
61
92
  asset
62
93
  end
63
94
 
64
- def load_asset_by_uri(uri, filename, params)
65
- # Internal assertion, should be routed through load_asset_by_id_uri
66
- if params.key?(:id)
67
- raise ArgumentError, "expected uri to have no id: #{uri}"
68
- end
69
-
70
- unless file?(filename)
71
- raise FileNotFound, "could not find file: #{filename}"
95
+ # Internal: Loads an asset and saves it to cache
96
+ #
97
+ # unloaded - An UnloadedAsset
98
+ #
99
+ # This method is only called when the given unloaded asset could not be
100
+ # successfully pulled from cache.
101
+ def load_from_unloaded(unloaded)
102
+ unless file?(unloaded.filename)
103
+ raise FileNotFound, "could not find file: #{unloaded.filename}"
72
104
  end
73
105
 
74
- load_path, logical_path = paths_split(config[:paths], filename)
106
+ load_path, logical_path = paths_split(config[:paths], unloaded.filename)
75
107
 
76
108
  unless load_path
77
- raise FileOutsidePaths, "#{filename} is no longer under a load path: #{self.paths.join(', ')}"
109
+ raise FileOutsidePaths, "#{unloaded.filename} is no longer under a load path: #{self.paths.join(', ')}"
78
110
  end
79
111
 
80
112
  logical_path, file_type, engine_extnames, _ = parse_path_extnames(logical_path)
81
- logical_path = normalize_logical_path(logical_path)
82
113
  name = logical_path
83
114
 
84
- if pipeline = params[:pipeline]
115
+ if pipeline = unloaded.params[:pipeline]
85
116
  logical_path += ".#{pipeline}"
86
117
  end
87
118
 
88
- if type = params[:type]
119
+ if type = unloaded.params[:type]
89
120
  logical_path += config[:mime_types][type][:extensions].first
90
121
  end
91
122
 
@@ -103,72 +134,190 @@ module Sprockets
103
134
  result = call_processors(processors, {
104
135
  environment: self,
105
136
  cache: self.cache,
106
- uri: uri,
107
- filename: filename,
137
+ uri: unloaded.uri,
138
+ filename: unloaded.filename,
108
139
  load_path: load_path,
109
140
  name: name,
110
141
  content_type: type,
111
142
  metadata: { dependencies: dependencies }
112
143
  })
144
+ validate_processor_result!(result)
113
145
  source = result.delete(:data)
114
- metadata = result.merge!(
115
- charset: source.encoding.name.downcase,
116
- digest: digest(source),
117
- length: source.bytesize
118
- )
146
+ metadata = result
147
+ metadata[:charset] = source.encoding.name.downcase unless metadata.key?(:charset)
148
+ metadata[:digest] = digest(source)
149
+ metadata[:length] = source.bytesize
119
150
  else
151
+ dependencies << build_file_digest_uri(unloaded.filename)
120
152
  metadata = {
121
- digest: file_digest(filename),
122
- length: self.stat(filename).size,
153
+ digest: file_digest(unloaded.filename),
154
+ length: self.stat(unloaded.filename).size,
123
155
  dependencies: dependencies
124
156
  }
125
157
  end
126
158
 
127
159
  asset = {
128
- uri: uri,
160
+ uri: unloaded.uri,
129
161
  load_path: load_path,
130
- filename: filename,
162
+ filename: unloaded.filename,
131
163
  name: name,
132
164
  logical_path: logical_path,
133
165
  content_type: type,
134
166
  source: source,
135
167
  metadata: metadata,
136
- integrity: integrity_uri(metadata[:digest], type),
137
- dependencies_digest: digest(resolve_dependencies(metadata[:dependencies]))
168
+ dependencies_digest: DigestUtils.digest(resolve_dependencies(metadata[:dependencies]))
138
169
  }
139
170
 
140
171
  asset[:id] = pack_hexdigest(digest(asset))
141
- asset[:uri] = build_asset_uri(filename, params.merge(id: asset[:id]))
172
+ asset[:uri] = build_asset_uri(unloaded.filename, unloaded.params.merge(id: asset[:id]))
142
173
 
143
174
  # Deprecated: Avoid tracking Asset mtime
144
175
  asset[:mtime] = metadata[:dependencies].map { |u|
145
176
  if u.start_with?("file-digest:")
146
177
  s = self.stat(parse_file_digest_uri(u))
147
- s ? s.mtime.to_i : 0
178
+ s ? s.mtime.to_i : nil
148
179
  else
149
- 0
180
+ nil
150
181
  end
151
- }.max
152
-
153
- cache.set("asset-uri:#{VERSION}:#{asset[:uri]}", asset, true)
154
- cache.set("asset-uri-digest:#{VERSION}:#{uri}:#{asset[:dependencies_digest]}", asset[:uri], true)
182
+ }.compact.max
183
+ asset[:mtime] ||= self.stat(unloaded.filename).mtime.to_i
155
184
 
185
+ store_asset(asset, unloaded)
156
186
  asset
157
187
  end
158
188
 
159
- def fetch_asset_from_dependency_cache(uri, filename, limit = 3)
160
- key = "asset-uri-cache-dependencies:#{VERSION}:#{uri}:#{file_digest(filename)}"
161
- history = cache.get(key) || []
189
+ # Internal: Save a given asset to the cache
190
+ #
191
+ # asset - A hash containing values of loaded asset
192
+ # unloaded - The UnloadedAsset used to lookup the `asset`
193
+ #
194
+ # This method converts all absolute paths to "compressed" paths
195
+ # which are relative if they're in the root.
196
+ def store_asset(asset, unloaded)
197
+ # Save the asset in the cache under the new URI
198
+ cached_asset = asset.dup
199
+ cached_asset[:uri] = compress_from_root(asset[:uri])
200
+ cached_asset[:filename] = compress_from_root(asset[:filename])
201
+ cached_asset[:load_path] = compress_from_root(asset[:load_path])
162
202
 
203
+ if cached_asset[:metadata]
204
+ # Deep dup to avoid modifying `asset`
205
+ cached_asset[:metadata] = cached_asset[:metadata].dup
206
+ if cached_asset[:metadata][:included] && !cached_asset[:metadata][:included].empty?
207
+ cached_asset[:metadata][:included] = cached_asset[:metadata][:included].dup
208
+ cached_asset[:metadata][:included].map! { |uri| compress_from_root(uri) }
209
+ end
210
+
211
+ if cached_asset[:metadata][:links] && !cached_asset[:metadata][:links].empty?
212
+ cached_asset[:metadata][:links] = cached_asset[:metadata][:links].dup
213
+ cached_asset[:metadata][:links].map! { |uri| compress_from_root(uri) }
214
+ end
215
+
216
+ if cached_asset[:metadata][:stubbed] && !cached_asset[:metadata][:stubbed].empty?
217
+ cached_asset[:metadata][:stubbed] = cached_asset[:metadata][:stubbed].dup
218
+ cached_asset[:metadata][:stubbed].map! { |uri| compress_from_root(uri) }
219
+ end
220
+
221
+ if cached_asset[:metadata][:required] && !cached_asset[:metadata][:required].empty?
222
+ cached_asset[:metadata][:required] = cached_asset[:metadata][:required].dup
223
+ cached_asset[:metadata][:required].map! { |uri| compress_from_root(uri) }
224
+ end
225
+
226
+ if cached_asset[:metadata][:dependencies] && !cached_asset[:metadata][:dependencies].empty?
227
+ cached_asset[:metadata][:dependencies] = cached_asset[:metadata][:dependencies].dup
228
+ cached_asset[:metadata][:dependencies].map! do |uri|
229
+ uri.start_with?("file-digest://".freeze) ? compress_from_root(uri) : uri
230
+ end
231
+ end
232
+
233
+ # compress all _dependencies in metadata like `sass_dependencies`
234
+ cached_asset[:metadata].each do |key, value|
235
+ next unless key =~ /_dependencies\z/
236
+ cached_asset[:metadata][key] = value.dup
237
+ cached_asset[:metadata][key].map! {|uri| compress_from_root(uri) }
238
+ end
239
+ end
240
+
241
+ # Unloaded asset and stored_asset now have a different URI
242
+ stored_asset = UnloadedAsset.new(asset[:uri], self)
243
+ cache.set(stored_asset.asset_key, cached_asset, true)
244
+
245
+ # Save the new relative path for the digest key of the unloaded asset
246
+ cache.set(unloaded.digest_key(asset[:dependencies_digest]), stored_asset.compressed_path, true)
247
+ end
248
+
249
+
250
+ # Internal: Resolve set of dependency URIs.
251
+ #
252
+ # uris - An Array of "dependencies" for example:
253
+ # ["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
254
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
255
+ # "processors:type=text/css&file_type=text/css&pipeline=self",
256
+ # "file-digest:///Full/path/app/assets/stylesheets"]
257
+ #
258
+ # Returns back array of things that the given uri dpends on
259
+ # For example the environment version, if you're using a different version of sprockets
260
+ # then the dependencies should be different, this is used only for generating cache key
261
+ # for example the "environment-version" may be resolved to "environment-1.0-3.2.0" for
262
+ # version "3.2.0" of sprockets.
263
+ #
264
+ # Any paths that are returned are converted to relative paths
265
+ #
266
+ # Returns array of resolved dependencies
267
+ def resolve_dependencies(uris)
268
+ uris.map { |uri| resolve_dependency(uri) }
269
+ end
270
+
271
+ # Internal: Retrieves an asset based on its digest
272
+ #
273
+ # unloaded - An UnloadedAsset
274
+ # limit - A Fixnum which sets the maximum number of versions of "histories"
275
+ # stored in the cache
276
+ #
277
+ # This method attempts to retrieve the last `limit` number of histories of an asset
278
+ # from the cache a "history" which is an array of unresolved "dependencies" that the asset needs
279
+ # to compile. In this case A dependency can refer to either an asset i.e. index.js
280
+ # may rely on jquery.js (so jquery.js is a depndency), or other factors that may affect
281
+ # compilation, such as the VERSION of sprockets (i.e. the environment) and what "processors"
282
+ # are used.
283
+ #
284
+ # For example a history array may look something like this
285
+ #
286
+ # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
287
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
288
+ # "processors:type=text/css&file_digesttype=text/css&pipeline=self",
289
+ # "file-digest:///Full/path/app/assets/stylesheets"]]
290
+ #
291
+ # Where the first entry is a Set of dependencies for last generated version of that asset.
292
+ # Multiple versions are stored since sprockets keeps the last `limit` number of assets
293
+ # generated present in the system.
294
+ #
295
+ # If a "history" of dependencies is present in the cache, each version of "history" will be
296
+ # yielded to the passed block which is responsible for loading the asset. If found, the existing
297
+ # history will be saved with the dependency that found a valid asset moved to the front.
298
+ #
299
+ # If no history is present, or if none of the histories could be resolved to a valid asset then,
300
+ # the block is yielded to and expected to return a valid asset.
301
+ # When this happens the dependencies for the returned asset are added to the "history", and older
302
+ # entries are removed if the "history" is above `limit`.
303
+ def fetch_asset_from_dependency_cache(unloaded, limit = 3)
304
+ key = unloaded.dependency_history_key
305
+
306
+ history = cache.get(key) || []
163
307
  history.each_with_index do |deps, index|
164
- if asset = yield(deps)
308
+ expanded_deps = deps.map do |path|
309
+ path.start_with?("file-digest://") ? expand_from_root(path) : path
310
+ end
311
+ if asset = yield(expanded_deps)
165
312
  cache.set(key, history.rotate!(index)) if index > 0
166
313
  return asset
167
314
  end
168
315
  end
169
316
 
170
317
  asset = yield
171
- deps = asset[:metadata][:dependencies]
318
+ deps = asset[:metadata][:dependencies].dup.map! do |uri|
319
+ uri.start_with?("file-digest://") ? compress_from_root(uri) : uri
320
+ end
172
321
  cache.set(key, history.unshift(deps).take(limit))
173
322
  asset
174
323
  end
@@ -1,6 +1,10 @@
1
1
  require 'json'
2
2
  require 'time'
3
+
4
+ require 'concurrent'
5
+
3
6
  require 'sprockets/manifest_utils'
7
+ require 'sprockets/utils/gzip'
4
8
 
5
9
  module Sprockets
6
10
  # The Manifest logs the contents of assets compiled to a single directory. It
@@ -145,6 +149,24 @@ module Sprockets
145
149
  nil
146
150
  end
147
151
 
152
+ # Public: Find the source of assets by paths.
153
+ #
154
+ # Returns Enumerator of assets file content.
155
+ def find_sources(*args)
156
+ return to_enum(__method__, *args) unless block_given?
157
+
158
+ if environment
159
+ find(*args).each do |asset|
160
+ yield asset.source
161
+ end
162
+ else
163
+ args.each do |path|
164
+ asset = assets[path]
165
+ yield File.binread(File.join(dir, asset)) if asset
166
+ end
167
+ end
168
+ end
169
+
148
170
  # Compile and write asset to directory. The asset is written to a
149
171
  # fingerprinted filename like
150
172
  # `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
@@ -157,7 +179,9 @@ module Sprockets
157
179
  raise Error, "manifest requires environment for compilation"
158
180
  end
159
181
 
160
- filenames = []
182
+ filenames = []
183
+ concurrent_compressors = []
184
+ concurrent_writers = []
161
185
 
162
186
  find(*args) do |asset|
163
187
  files[asset.digest_path] = {
@@ -165,21 +189,46 @@ module Sprockets
165
189
  'mtime' => asset.mtime.iso8601,
166
190
  'size' => asset.bytesize,
167
191
  'digest' => asset.hexdigest,
168
- 'integrity' => asset.integrity
192
+
193
+ # Deprecated: Remove beta integrity attribute in next release.
194
+ # Callers should DigestUtils.hexdigest_integrity_uri to compute the
195
+ # digest themselves.
196
+ 'integrity' => DigestUtils.hexdigest_integrity_uri(asset.hexdigest)
169
197
  }
170
198
  assets[asset.logical_path] = asset.digest_path
171
199
 
200
+ if alias_logical_path = self.class.compute_alias_logical_path(asset.logical_path)
201
+ assets[alias_logical_path] = asset.digest_path
202
+ end
203
+
172
204
  target = File.join(dir, asset.digest_path)
173
205
 
174
206
  if File.exist?(target)
175
207
  logger.debug "Skipping #{target}, already exists"
176
208
  else
177
209
  logger.info "Writing #{target}"
178
- asset.write_to target
210
+ write_file = Concurrent::Future.execute { asset.write_to target }
211
+ concurrent_writers << write_file
179
212
  end
180
-
181
213
  filenames << asset.filename
214
+
215
+ next if environment.skip_gzip?
216
+ gzip = Utils::Gzip.new(asset)
217
+ next if gzip.cannot_compress?(environment.mime_types)
218
+
219
+ if File.exist?("#{target}.gz")
220
+ logger.debug "Skipping #{target}.gz, already exists"
221
+ else
222
+ logger.info "Writing #{target}.gz"
223
+ concurrent_compressors << Concurrent::Future.execute do
224
+ write_file.wait! if write_file
225
+ gzip.compress(target)
226
+ end
227
+ end
228
+
182
229
  end
230
+ concurrent_writers.each(&:wait!)
231
+ concurrent_compressors.each(&:wait!)
183
232
  save
184
233
 
185
234
  filenames
@@ -192,6 +241,7 @@ module Sprockets
192
241
  #
193
242
  def remove(filename)
194
243
  path = File.join(dir, filename)
244
+ gzip = "#{path}.gz"
195
245
  logical_path = files[filename]['logical_path']
196
246
 
197
247
  if assets[logical_path] == filename
@@ -200,6 +250,7 @@ module Sprockets
200
250
 
201
251
  files.delete(filename)
202
252
  FileUtils.rm(path) if File.exist?(path)
253
+ FileUtils.rm(gzip) if File.exist?(gzip)
203
254
 
204
255
  save
205
256
 
@@ -230,9 +281,9 @@ module Sprockets
230
281
  # Sort by timestamp
231
282
  Time.parse(attrs['mtime'])
232
283
  }.reverse.each_with_index.drop_while { |(_, attrs), index|
233
- age = [0, Time.now - Time.parse(attrs['mtime'])].max
284
+ _age = [0, Time.now - Time.parse(attrs['mtime'])].max
234
285
  # Keep if under age or within the count limit
235
- age < age || index < count
286
+ _age < age || index < count
236
287
  }.each { |(path, _), _|
237
288
  # Remove old assets
238
289
  remove(path)
@@ -111,17 +111,37 @@ module Sprockets
111
111
  def compute_extname_map
112
112
  graph = {}
113
113
 
114
+ engine_extname_permutation = []
115
+
116
+ 4.times do |n|
117
+ config[:engines].keys.permutation(n).each do |engine_extnames|
118
+ engine_extname_permutation << engine_extnames
119
+ end
120
+ end
121
+
122
+ mime_exts_grouped_by_mime_type = {}
123
+ config[:mime_exts].each do |format_extname,format_type|
124
+ mime_exts_grouped_by_mime_type[format_type] ||= []
125
+ mime_exts_grouped_by_mime_type[format_type] << format_extname
126
+ end
127
+
114
128
  ([nil] + pipelines.keys.map(&:to_s)).each do |pipeline|
115
- pipeline_extname = ".#{pipeline}" if pipeline
116
- ([[nil, nil]] + config[:mime_exts].to_a).each do |format_extname, format_type|
117
- 4.times do |n|
118
- config[:engines].keys.permutation(n).each do |engine_extnames|
129
+ pipeline_extname = pipeline ? ".#{pipeline}" : ''.freeze
130
+ engine_extname_permutation.each do |engine_extnames|
131
+ mime_exts_grouped_by_mime_type.each do |format_type, format_extnames|
132
+ type = format_type
133
+ value = [type, engine_extnames, pipeline]
134
+ format_extnames.each do |format_extname|
119
135
  key = "#{pipeline_extname}#{format_extname}#{engine_extnames.join}"
120
- type = format_type || config[:engine_mime_types][engine_extnames.first]
121
- graph[key] = {type: type, engines: engine_extnames, pipeline: pipeline}
136
+ graph[key] = value
137
+ end
138
+ if format_type == config[:engine_mime_types][engine_extnames.first]
139
+ key = "#{pipeline_extname}#{engine_extnames.join}"
140
+ graph[key] = value
122
141
  end
123
142
  end
124
143
  end
144
+ graph[pipeline_extname] = [nil, [], pipeline]
125
145
  end
126
146
 
127
147
  graph
@@ -1,5 +1,3 @@
1
- require 'fileutils'
2
-
3
1
  module Sprockets
4
2
  # Internal: File and path related utilities. Mixed into Environment.
5
3
  #
@@ -55,9 +53,13 @@ module Sprockets
55
53
  # Returns an empty `Array` if the directory does not exist.
56
54
  def entries(path)
57
55
  if File.directory?(path)
58
- Dir.entries(path, :encoding => Encoding.default_internal).reject! { |entry|
59
- entry =~ /^\.|~$|^\#.*\#$/
60
- }.sort!
56
+ entries = Dir.entries(path, :encoding => Encoding.default_internal)
57
+ entries.reject! { |entry|
58
+ entry.start_with?(".".freeze) ||
59
+ (entry.start_with?("#".freeze) && entry.end_with?("#".freeze)) ||
60
+ entry.end_with?("~".freeze)
61
+ }
62
+ entries.sort!
61
63
  else
62
64
  []
63
65
  end
@@ -146,16 +148,19 @@ module Sprockets
146
148
  #
147
149
  # Returns [String extname, Object value] or nil nothing matched.
148
150
  def match_path_extname(path, extensions)
149
- match, key = nil, ""
150
- path_extnames(path).reverse_each do |extname|
151
- key.prepend(extname)
152
- if value = extensions[key]
153
- match = [key.dup, value]
154
- elsif match
155
- break
151
+ basename = File.basename(path)
152
+
153
+ i = basename.index('.'.freeze)
154
+ while i && i < basename.length - 1
155
+ extname = basename[i..-1]
156
+ if value = extensions[extname]
157
+ return extname, value
156
158
  end
159
+
160
+ i = basename.index('.'.freeze, i+1)
157
161
  end
158
- match
162
+
163
+ nil
159
164
  end
160
165
 
161
166
  # Internal: Returns all parents for path
@@ -274,9 +279,9 @@ module Sprockets
274
279
  yield f
275
280
  end
276
281
 
277
- FileUtils.mv(tmpname, filename)
282
+ File.rename(tmpname, filename)
278
283
  ensure
279
- FileUtils.rm(tmpname) if File.exist?(tmpname)
284
+ File.delete(tmpname) if File.exist?(tmpname)
280
285
  end
281
286
  end
282
287
  end
@@ -231,14 +231,24 @@ module Sprockets
231
231
  compute_transformers!
232
232
  end
233
233
 
234
+ def deprecate_legacy_processor_interface(interface)
235
+ msg = "You are using a deprecated processor interface #{ interface.inspect }.\n" +
236
+ "Please update your processor interface:\n" +
237
+ "https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors\n"
238
+
239
+ Deprecation.new([caller[3]]).warn msg
240
+ end
241
+
234
242
  def wrap_processor(klass, proc)
235
243
  if !proc
236
244
  if klass.respond_to?(:call)
237
245
  klass
238
246
  else
247
+ deprecate_legacy_processor_interface(klass)
239
248
  LegacyTiltProcessor.new(klass)
240
249
  end
241
250
  elsif proc.respond_to?(:arity) && proc.arity == 2
251
+ deprecate_legacy_processor_interface(proc)
242
252
  LegacyProcProcessor.new(klass.to_s, proc)
243
253
  else
244
254
  proc