sprockets 2.12.5 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sprockets might be problematic. Click here for more details.

Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +2 -2
  3. data/README.md +61 -34
  4. data/lib/rake/sprocketstask.rb +5 -4
  5. data/lib/sprockets.rb +123 -85
  6. data/lib/sprockets/asset.rb +161 -200
  7. data/lib/sprockets/asset_uri.rb +64 -0
  8. data/lib/sprockets/base.rb +138 -373
  9. data/lib/sprockets/bower.rb +56 -0
  10. data/lib/sprockets/bundle.rb +32 -0
  11. data/lib/sprockets/cache.rb +220 -0
  12. data/lib/sprockets/cache/file_store.rb +145 -13
  13. data/lib/sprockets/cache/memory_store.rb +66 -0
  14. data/lib/sprockets/cache/null_store.rb +46 -0
  15. data/lib/sprockets/cached_environment.rb +103 -0
  16. data/lib/sprockets/closure_compressor.rb +30 -12
  17. data/lib/sprockets/coffee_script_template.rb +23 -0
  18. data/lib/sprockets/compressing.rb +20 -25
  19. data/lib/sprockets/configuration.rb +95 -0
  20. data/lib/sprockets/context.rb +68 -131
  21. data/lib/sprockets/directive_processor.rb +138 -179
  22. data/lib/sprockets/eco_template.rb +10 -19
  23. data/lib/sprockets/ejs_template.rb +10 -19
  24. data/lib/sprockets/encoding_utils.rb +246 -0
  25. data/lib/sprockets/engines.rb +40 -29
  26. data/lib/sprockets/environment.rb +10 -66
  27. data/lib/sprockets/erb_template.rb +23 -0
  28. data/lib/sprockets/errors.rb +5 -13
  29. data/lib/sprockets/http_utils.rb +97 -0
  30. data/lib/sprockets/jst_processor.rb +28 -15
  31. data/lib/sprockets/lazy_processor.rb +15 -0
  32. data/lib/sprockets/legacy.rb +23 -0
  33. data/lib/sprockets/legacy_proc_processor.rb +35 -0
  34. data/lib/sprockets/legacy_tilt_processor.rb +29 -0
  35. data/lib/sprockets/manifest.rb +128 -99
  36. data/lib/sprockets/mime.rb +114 -33
  37. data/lib/sprockets/path_utils.rb +179 -0
  38. data/lib/sprockets/paths.rb +13 -26
  39. data/lib/sprockets/processing.rb +198 -107
  40. data/lib/sprockets/resolve.rb +289 -0
  41. data/lib/sprockets/sass_compressor.rb +36 -17
  42. data/lib/sprockets/sass_template.rb +269 -46
  43. data/lib/sprockets/server.rb +113 -83
  44. data/lib/sprockets/transformers.rb +69 -0
  45. data/lib/sprockets/uglifier_compressor.rb +36 -15
  46. data/lib/sprockets/utils.rb +161 -44
  47. data/lib/sprockets/version.rb +1 -1
  48. data/lib/sprockets/yui_compressor.rb +37 -12
  49. metadata +64 -106
  50. data/lib/sprockets/asset_attributes.rb +0 -137
  51. data/lib/sprockets/bundled_asset.rb +0 -78
  52. data/lib/sprockets/caching.rb +0 -96
  53. data/lib/sprockets/charset_normalizer.rb +0 -41
  54. data/lib/sprockets/index.rb +0 -100
  55. data/lib/sprockets/processed_asset.rb +0 -152
  56. data/lib/sprockets/processor.rb +0 -32
  57. data/lib/sprockets/safety_colons.rb +0 -28
  58. data/lib/sprockets/sass_cache_store.rb +0 -29
  59. data/lib/sprockets/sass_functions.rb +0 -70
  60. data/lib/sprockets/sass_importer.rb +0 -30
  61. data/lib/sprockets/scss_template.rb +0 -13
  62. data/lib/sprockets/static_asset.rb +0 -60
@@ -0,0 +1,23 @@
1
+ require 'erb'
2
+
3
+ module Sprockets
4
+ class ERBTemplate
5
+ def self.call(input)
6
+ new.call(input)
7
+ end
8
+
9
+ def initialize(&block)
10
+ @block = block
11
+ end
12
+
13
+ def call(input)
14
+ engine = ::ERB.new(input[:data], nil, '<>')
15
+ context = input[:environment].context_class.new(input)
16
+ klass = (class << context; self; end)
17
+ klass.class_eval(&@block) if @block
18
+ engine.def_method(klass, :_evaluate_template, input[:filename])
19
+ data = context._evaluate_template
20
+ context.metadata.merge(data: data)
21
+ end
22
+ end
23
+ end
@@ -2,19 +2,11 @@
2
2
  module Sprockets
3
3
  class Error < StandardError; end
4
4
  class ArgumentError < Error; end
5
- class CircularDependencyError < Error; end
6
5
  class ContentTypeMismatch < Error; end
7
- class EncodingError < Error; end
8
- class FileNotFound < Error; end
9
- class FileOutsidePaths < Error; end
10
6
  class NotImplementedError < Error; end
11
- class UnserializeError < Error; end
12
-
13
- module EngineError
14
- attr_accessor :sprockets_annotation
15
-
16
- def message
17
- [super, sprockets_annotation].compact.join("\n")
18
- end
19
- end
7
+ class NotFound < Error; end
8
+ class ConversionError < NotFound; end
9
+ class FileNotFound < NotFound; end
10
+ class FileOutsidePaths < NotFound; end
11
+ class VersionNotFound < NotFound; end
20
12
  end
@@ -0,0 +1,97 @@
1
+ module Sprockets
2
+ module HTTPUtils
3
+ extend self
4
+
5
+ # Internal: Test mime type against mime range.
6
+ #
7
+ # match_mime_type?('text/html', 'text/*') => true
8
+ # match_mime_type?('text/plain', '*') => true
9
+ # match_mime_type?('text/html', 'application/json') => false
10
+ #
11
+ # Returns true if the given value is a mime match for the given mime match
12
+ # specification, false otherwise.
13
+ def match_mime_type?(value, matcher)
14
+ v1, v2 = value.split('/', 2)
15
+ m1, m2 = matcher.split('/', 2)
16
+ (m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2)
17
+ end
18
+
19
+ # Internal: Parse Accept header quality values.
20
+ #
21
+ # Adapted from Rack::Utils#q_values.
22
+ #
23
+ # Returns an Array of [String, Float].
24
+ def parse_q_values(values)
25
+ values.to_s.split(/\s*,\s*/).map do |part|
26
+ value, parameters = part.split(/\s*;\s*/, 2)
27
+ quality = 1.0
28
+ if md = /\Aq=([\d.]+)/.match(parameters)
29
+ quality = md[1].to_f
30
+ end
31
+ [value, quality]
32
+ end
33
+ end
34
+
35
+ # Internal: Find all qvalue matches from an Array of available options.
36
+ #
37
+ # Adapted from Rack::Utils#q_values.
38
+ #
39
+ # Returns Array of matched Strings from available Array or [].
40
+ def find_q_matches(q_values, available, &matcher)
41
+ matcher ||= lambda { |a, b| a == b }
42
+
43
+ matches = []
44
+
45
+ case q_values
46
+ when Array
47
+ when String
48
+ q_values = parse_q_values(q_values)
49
+ when NilClass
50
+ q_values = []
51
+ else
52
+ raise TypeError, "unknown q_values type: #{q_values.class}"
53
+ end
54
+
55
+ q_values.each do |accepted, quality|
56
+ if match = available.find { |option| matcher.call(option, accepted) }
57
+ matches << [match, quality]
58
+ end
59
+ end
60
+
61
+ matches.sort_by { |match, quality| -quality }.map { |match, quality| match }
62
+ end
63
+
64
+ # Internal: Find the best qvalue match from an Array of available options.
65
+ #
66
+ # Adapted from Rack::Utils#q_values.
67
+ #
68
+ # Returns the matched String from available Array or nil.
69
+ def find_best_q_match(q_values, available, &matcher)
70
+ find_q_matches(q_values, available, &matcher).first
71
+ end
72
+
73
+ # Internal: Find the all qvalue match from an Array of available mime type
74
+ # options.
75
+ #
76
+ # Adapted from Rack::Utils#q_values.
77
+ #
78
+ # Returns Array of matched mime type Strings from available Array or [].
79
+ def find_mime_type_matches(q_value_header, available)
80
+ find_q_matches(q_value_header, available) do |a, b|
81
+ match_mime_type?(a, b)
82
+ end
83
+ end
84
+
85
+ # Internal: Find the best qvalue match from an Array of available mime type
86
+ # options.
87
+ #
88
+ # Adapted from Rack::Utils#q_values.
89
+ #
90
+ # Returns the matched mime type String from available Array or nil.
91
+ def find_best_mime_type_match(q_value_header, available)
92
+ find_best_q_match(q_value_header, available) do |a, b|
93
+ match_mime_type?(a, b)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,29 +1,42 @@
1
- require 'tilt'
2
-
3
1
  module Sprockets
4
- class JstProcessor < Tilt::Template
5
- self.default_mime_type = 'application/javascript'
6
-
2
+ # Public: .jst engine.
3
+ #
4
+ # Exports server side compiled templates to an object.
5
+ #
6
+ # Name your template "users/show.jst.ejs", "users/new.jst.eco", etc.
7
+ #
8
+ # To accept the default options
9
+ #
10
+ # environment.register_engine '.jst',
11
+ # JstProcessor,
12
+ # mime_type: 'application/javascript'
13
+ #
14
+ # Change the default namespace.
15
+ #
16
+ # environment.register_engine '.jst',
17
+ # JstProcessor.new(namespace: 'App.templates'),
18
+ # mime_type: 'application/javascript'
19
+ #
20
+ class JstProcessor
7
21
  def self.default_namespace
8
22
  'this.JST'
9
23
  end
10
24
 
11
- def prepare
12
- @namespace = self.class.default_namespace
25
+ def self.call(*args)
26
+ new.call(*args)
13
27
  end
14
28
 
15
- attr_reader :namespace
29
+ def initialize(options = {})
30
+ @namespace = options[:namespace] || self.class.default_namespace
31
+ end
16
32
 
17
- def evaluate(scope, locals, &block)
33
+ def call(input)
34
+ data = input[:data].gsub(/$(.)/m, "\\1 ").strip
35
+ key = input[:name]
18
36
  <<-JST
19
- (function() { #{namespace} || (#{namespace} = {}); #{namespace}[#{scope.logical_path.inspect}] = #{indent(data)};
37
+ (function() { #{@namespace} || (#{@namespace} = {}); #{@namespace}[#{key.inspect}] = #{data};
20
38
  }).call(this);
21
39
  JST
22
40
  end
23
-
24
- private
25
- def indent(string)
26
- string.gsub(/$(.)/m, "\\1 ").strip
27
- end
28
41
  end
29
42
  end
@@ -0,0 +1,15 @@
1
+ module Sprockets
2
+ # Internal: Used for lazy loading processors.
3
+ #
4
+ # LazyProcessor.new { CoffeeScriptTemplate }
5
+ #
6
+ class LazyProcessor
7
+ def initialize(&block)
8
+ @block = block
9
+ end
10
+
11
+ def unwrap
12
+ @obj ||= @block.call
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module Sprockets
2
+ module Legacy
3
+ private
4
+ # Deprecated: Seriously.
5
+ def matches_filter(filters, logical_path, filename)
6
+ return true if filters.empty?
7
+
8
+ filters.any? do |filter|
9
+ if filter.is_a?(Regexp)
10
+ filter.match(logical_path)
11
+ elsif filter.respond_to?(:call)
12
+ if filter.arity == 1
13
+ filter.call(logical_path)
14
+ else
15
+ filter.call(logical_path, filename.to_s)
16
+ end
17
+ else
18
+ File.fnmatch(filter.to_s, logical_path)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ require 'delegate'
2
+
3
+ module Sprockets
4
+ # Deprecated: Wraps legacy process Procs with new processor call signature.
5
+ #
6
+ # Will be removed in Sprockets 4.x.
7
+ #
8
+ # LegacyProcProcessor.new(:compress,
9
+ # proc { |context, data| data.gsub(...) })
10
+ #
11
+ class LegacyProcProcessor < Delegator
12
+ def initialize(name, proc)
13
+ @name = name
14
+ @proc = proc
15
+ end
16
+
17
+ def __getobj__
18
+ @proc
19
+ end
20
+
21
+ def name
22
+ "Sprockets::LegacyProcProcessor (#{@name})"
23
+ end
24
+
25
+ def to_s
26
+ name
27
+ end
28
+
29
+ def call(input)
30
+ context = input[:environment].context_class.new(input)
31
+ data = @proc.call(context, input[:data])
32
+ context.metadata.merge(data: data)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ require 'delegate'
2
+
3
+ module Sprockets
4
+ # Deprecated: Wraps legacy engine and process Tilt templates with new
5
+ # processor call signature.
6
+ #
7
+ # Will be removed in Sprockets 4.x.
8
+ #
9
+ # LegacyTiltProcessor.new(Tilt::CoffeeScriptTemplate)
10
+ #
11
+ class LegacyTiltProcessor < Delegator
12
+ def initialize(klass)
13
+ @klass = klass
14
+ end
15
+
16
+ def __getobj__
17
+ @klass
18
+ end
19
+
20
+ def call(input)
21
+ filename = input[:filename]
22
+ data = input[:data]
23
+ context = input[:environment].context_class.new(input)
24
+
25
+ data = @klass.new(filename) { data }.render(context)
26
+ context.metadata.merge(data: data)
27
+ end
28
+ end
29
+ end
@@ -1,26 +1,25 @@
1
- require 'multi_json'
1
+ require 'json'
2
2
  require 'securerandom'
3
3
  require 'time'
4
4
 
5
5
  module Sprockets
6
- # The Manifest logs the contents of assets compiled to a single
7
- # directory. It records basic attributes about the asset for fast
8
- # lookup without having to compile. A pointer from each logical path
9
- # indicates with fingerprinted asset is the current one.
6
+ # The Manifest logs the contents of assets compiled to a single directory. It
7
+ # records basic attributes about the asset for fast lookup without having to
8
+ # compile. A pointer from each logical path indicates which fingerprinted
9
+ # asset is the current one.
10
10
  #
11
- # The JSON is part of the public API and should be considered
12
- # stable. This should make it easy to read from other programming
13
- # languages and processes that don't have sprockets loaded. See
14
- # `#assets` and `#files` for more infomation about the structure.
11
+ # The JSON is part of the public API and should be considered stable. This
12
+ # should make it easy to read from other programming languages and processes
13
+ # that don't have sprockets loaded. See `#assets` and `#files` for more
14
+ # infomation about the structure.
15
15
  class Manifest
16
- attr_reader :environment, :path, :dir
17
-
18
- # Create new Manifest associated with an `environment`. `path` is
19
- # a full path to the manifest json file. The file may or may not
20
- # already exist. The dirname of the `path` will be used to write
21
- # compiled assets to. Otherwise, if the path is a directory, the
22
- # filename will default a random "manifest-123.json" file in that
23
- # directory.
16
+ attr_reader :environment
17
+
18
+ # Create new Manifest associated with an `environment`. `filename` is a full
19
+ # path to the manifest json file. The file may or may not already exist. The
20
+ # dirname of the `filename` will be used to write compiled assets to.
21
+ # Otherwise, if the path is a directory, the filename will default a random
22
+ # "manifest-123.json" file in that directory.
24
23
  #
25
24
  # Manifest.new(environment, "./public/assets/manifest.json")
26
25
  #
@@ -29,48 +28,55 @@ module Sprockets
29
28
  @environment = args.shift
30
29
  end
31
30
 
32
- @dir, @path = args[0], args[1]
31
+ @directory, @filename = args[0], args[1]
33
32
 
34
33
  # Expand paths
35
- @dir = File.expand_path(@dir) if @dir
36
- @path = File.expand_path(@path) if @path
34
+ @directory = File.expand_path(@directory) if @directory
35
+ @filename = File.expand_path(@filename) if @filename
37
36
 
38
- # If path is given as the second arg
39
- if @dir && File.extname(@dir) != ""
40
- @dir, @path = nil, @dir
37
+ # If filename is given as the second arg
38
+ if @directory && File.extname(@directory) != ""
39
+ @directory, @filename = nil, @directory
41
40
  end
42
41
 
43
- # Default dir to the directory of the path
44
- @dir ||= File.dirname(@path) if @path
42
+ # Default dir to the directory of the filename
43
+ @directory ||= File.dirname(@filename) if @filename
45
44
 
46
- # If directory is given w/o path, pick a random manifest.json location
47
- if @dir && @path.nil?
45
+ # If directory is given w/o filename, pick a random manifest.json location
46
+ if @directory && @filename.nil?
48
47
  # Find the first manifest.json in the directory
49
- paths = Dir[File.join(@dir, "manifest*.json")]
50
- if paths.any?
51
- @path = paths.first
48
+ filenames = Dir[File.join(@directory, "manifest*.json")]
49
+ if filenames.any?
50
+ @filename = filenames.first
52
51
  else
53
- @path = File.join(@dir, "manifest-#{SecureRandom.hex(16)}.json")
52
+ @filename = File.join(@directory, "manifest-#{SecureRandom.hex(16)}.json")
54
53
  end
55
54
  end
56
55
 
57
- unless @dir && @path
58
- raise ArgumentError, "manifest requires output path"
56
+ unless @directory && @filename
57
+ raise ArgumentError, "manifest requires output filename"
59
58
  end
60
59
 
61
- data = nil
60
+ data = {}
62
61
 
63
62
  begin
64
- if File.exist?(@path)
65
- data = json_decode(File.read(@path))
63
+ if File.exist?(@filename)
64
+ data = json_decode(File.read(@filename))
66
65
  end
67
- rescue MultiJson::DecodeError => e
68
- logger.error "#{@path} is invalid: #{e.class} #{e.message}"
66
+ rescue JSON::ParserError => e
67
+ logger.error "#{@filename} is invalid: #{e.class} #{e.message}"
69
68
  end
70
69
 
71
- @data = data.is_a?(Hash) ? data : {}
70
+ @data = data
72
71
  end
73
72
 
73
+ # Returns String path to manifest.json file.
74
+ attr_reader :filename
75
+ alias_method :path, :filename
76
+
77
+ attr_reader :directory
78
+ alias_method :dir, :directory
79
+
74
80
  # Returns internal assets mapping. Keys are logical paths which
75
81
  # map to the latest fingerprinted filename.
76
82
  #
@@ -100,6 +106,53 @@ module Sprockets
100
106
  @data['files'] ||= {}
101
107
  end
102
108
 
109
+ # Internal: Compile logical path matching filter into a proc that can be
110
+ # passed to logical_paths.select(&proc).
111
+ #
112
+ # compile_match_filter(proc { |logical_path|
113
+ # File.extname(logical_path) == '.js'
114
+ # })
115
+ #
116
+ # compile_match_filter(/application.js/)
117
+ #
118
+ # compile_match_filter("foo/*.js")
119
+ #
120
+ # Returns a Proc or raise a TypeError.
121
+ def self.compile_match_filter(filter)
122
+ # If the filter is already a proc, great nothing to do.
123
+ if filter.respond_to?(:call)
124
+ filter
125
+ # If the filter is a regexp, wrap it in a proc that tests it against the
126
+ # logical path.
127
+ elsif filter.is_a?(Regexp)
128
+ proc { |logical_path| filter.match(logical_path) }
129
+ elsif filter.is_a?(String)
130
+ # If its an absolute path, detect the matching full filename
131
+ if PathUtils.absolute_path?(filter)
132
+ proc { |logical_path, filename| filename == filter.to_s }
133
+ else
134
+ # Otherwise do an fnmatch against the logical path.
135
+ proc { |logical_path| File.fnmatch(filter.to_s, logical_path) }
136
+ end
137
+ else
138
+ raise TypeError, "unknown filter type: #{filter.inspect}"
139
+ end
140
+ end
141
+
142
+ # Public: Filter logical paths in environment. Useful for selecting what
143
+ # files you want to compile.
144
+ #
145
+ # Returns an Enumerator.
146
+ def filter_logical_paths(*args)
147
+ filters = args.flatten.map { |arg| self.class.compile_match_filter(arg) }
148
+ environment.logical_paths.select do |a, b|
149
+ filters.any? { |f| f.call(a, b) }
150
+ end
151
+ end
152
+
153
+ # Deprecated alias.
154
+ alias_method :find_logical_paths, :filter_logical_paths
155
+
103
156
  # Compile and write asset to directory. The asset is written to a
104
157
  # fingerprinted filename like
105
158
  # `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
@@ -112,11 +165,10 @@ module Sprockets
112
165
  raise Error, "manifest requires environment for compilation"
113
166
  end
114
167
 
115
- paths = environment.each_logical_path(*args).to_a +
116
- args.flatten.select { |fn| Pathname.new(fn).absolute? if fn.is_a?(String)}
168
+ filenames = []
117
169
 
118
- paths.each do |path|
119
- if asset = find_asset(path)
170
+ filter_logical_paths(*args).each do |_, filename|
171
+ find_assets(filename) do |asset|
120
172
  files[asset.digest_path] = {
121
173
  'logical_path' => asset.logical_path,
122
174
  'mtime' => asset.mtime.iso8601,
@@ -132,13 +184,14 @@ module Sprockets
132
184
  else
133
185
  logger.info "Writing #{target}"
134
186
  asset.write_to target
135
- asset.write_to "#{target}.gz" if asset.is_a?(BundledAsset)
136
187
  end
137
188
 
189
+ filenames << filename
138
190
  end
139
191
  end
140
192
  save
141
- paths
193
+
194
+ filenames
142
195
  end
143
196
 
144
197
  # Removes file from directory and from manifest. `filename` must
@@ -148,7 +201,6 @@ module Sprockets
148
201
  #
149
202
  def remove(filename)
150
203
  path = File.join(dir, filename)
151
- gzip = "#{path}.gz"
152
204
  logical_path = files[filename]['logical_path']
153
205
 
154
206
  if assets[logical_path] == filename
@@ -157,7 +209,6 @@ module Sprockets
157
209
 
158
210
  files.delete(filename)
159
211
  FileUtils.rm(path) if File.exist?(path)
160
- FileUtils.rm(gzip) if File.exist?(gzip)
161
212
 
162
213
  save
163
214
 
@@ -169,77 +220,61 @@ module Sprockets
169
220
  # Cleanup old assets in the compile directory. By default it will
170
221
  # keep the latest version plus 2 backups.
171
222
  def clean(keep = 2)
172
- self.assets.keys.each do |logical_path|
173
- # Get assets sorted by ctime, newest first
174
- assets = backups_for(logical_path)
223
+ asset_versions = files.group_by { |_, attrs| attrs['logical_path'] }
224
+
225
+ asset_versions.each do |logical_path, versions|
226
+ current = assets[logical_path]
227
+
228
+ backups = versions.reject { |path, _|
229
+ path == current
230
+ }.sort_by { |_, attrs|
231
+ # Sort by timestamp
232
+ Time.parse(attrs['mtime'])
233
+ }.reverse
175
234
 
176
235
  # Keep the last N backups
177
- assets = assets[keep..-1] || []
236
+ backups = backups[keep..-1] || []
178
237
 
179
238
  # Remove old assets
180
- assets.each { |path, _| remove(path) }
239
+ backups.each { |path, _| remove(path) }
181
240
  end
182
241
  end
183
242
 
184
243
  # Wipe directive
185
244
  def clobber
186
- FileUtils.rm_r(@dir) if File.exist?(@dir)
187
- logger.info "Removed #{@dir}"
245
+ FileUtils.rm_r(directory) if File.exist?(directory)
246
+ logger.info "Removed #{directory}"
188
247
  nil
189
248
  end
190
249
 
191
250
  protected
192
- # Finds all the backup assets for a logical path. The latest
193
- # version is always excluded. The return array is sorted by the
194
- # assets mtime in descending order (Newest to oldest).
195
- def backups_for(logical_path)
196
- files.select { |filename, attrs|
197
- # Matching logical paths
198
- attrs['logical_path'] == logical_path &&
199
- # Excluding whatever asset is the current
200
- assets[logical_path] != filename
201
- }.sort_by { |filename, attrs|
202
- # Sort by timestamp
203
- Time.parse(attrs['mtime'])
204
- }.reverse
205
- end
206
-
207
251
  # Basic wrapper around Environment#find_asset. Logs compile time.
208
- def find_asset(logical_path)
209
- asset = nil
210
- ms = benchmark do
211
- asset = environment.find_asset(logical_path)
252
+ def find_assets(path)
253
+ start = Utils.benchmark_start
254
+ environment.find_all_linked_assets(path) do |asset|
255
+ logger.debug do
256
+ "Compiled #{asset.logical_path} (#{Utils.benchmark_end(start)}ms)"
257
+ end
258
+ yield asset
259
+ start = Utils.benchmark_start
212
260
  end
213
- logger.debug "Compiled #{logical_path} (#{ms}ms)"
214
- asset
215
261
  end
216
262
 
217
263
  # Persist manfiest back to FS
218
264
  def save
219
- FileUtils.mkdir_p File.dirname(path)
220
- File.open(path, 'w') do |f|
265
+ FileUtils.mkdir_p File.dirname(filename)
266
+ File.open(filename, 'w') do |f|
221
267
  f.write json_encode(@data)
222
268
  end
223
269
  end
224
270
 
225
271
  private
226
- # Feature detect newer MultiJson API
227
- if MultiJson.respond_to?(:dump)
228
- def json_decode(obj)
229
- MultiJson.load(obj)
230
- end
231
-
232
- def json_encode(obj)
233
- MultiJson.dump(obj)
234
- end
235
- else
236
- def json_decode(obj)
237
- MultiJson.decode(obj)
238
- end
272
+ def json_decode(obj)
273
+ JSON.parse(obj, create_additions: false)
274
+ end
239
275
 
240
- def json_encode(obj)
241
- MultiJson.encode(obj)
242
- end
276
+ def json_encode(obj)
277
+ JSON.generate(obj)
243
278
  end
244
279
 
245
280
  def logger
@@ -251,11 +286,5 @@ module Sprockets
251
286
  logger
252
287
  end
253
288
  end
254
-
255
- def benchmark
256
- start_time = Time.now.to_f
257
- yield
258
- ((Time.now.to_f - start_time) * 1000).to_i
259
- end
260
289
  end
261
290
  end