sprockets 2.2.3 → 2.12.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,24 +1,24 @@
1
1
  require 'sprockets/asset_attributes'
2
2
  require 'sprockets/bundled_asset'
3
3
  require 'sprockets/caching'
4
+ require 'sprockets/errors'
4
5
  require 'sprockets/processed_asset'
5
- require 'sprockets/processing'
6
6
  require 'sprockets/server'
7
7
  require 'sprockets/static_asset'
8
- require 'sprockets/trail'
8
+ require 'multi_json'
9
9
  require 'pathname'
10
10
 
11
11
  module Sprockets
12
12
  # `Base` class for `Environment` and `Index`.
13
13
  class Base
14
- include Caching, Processing, Server, Trail
14
+ include Caching, Paths, Mime, Processing, Compressing, Engines, Server
15
15
 
16
16
  # Returns a `Digest` implementation class.
17
17
  #
18
18
  # Defaults to `Digest::MD5`.
19
19
  attr_reader :digest_class
20
20
 
21
- # Assign a `Digest` implementation class. This maybe any Ruby
21
+ # Assign a `Digest` implementation class. This may be any Ruby
22
22
  # `Digest::` implementation such as `Digest::MD5` or
23
23
  # `Digest::SHA1`.
24
24
  #
@@ -98,6 +98,114 @@ module Sprockets
98
98
  @cache = cache
99
99
  end
100
100
 
101
+ def prepend_path(path)
102
+ # Overrides the global behavior to expire the index
103
+ expire_index!
104
+ super
105
+ end
106
+
107
+ def append_path(path)
108
+ # Overrides the global behavior to expire the index
109
+ expire_index!
110
+ super
111
+ end
112
+
113
+ def clear_paths
114
+ # Overrides the global behavior to expire the index
115
+ expire_index!
116
+ super
117
+ end
118
+
119
+ # Finds the expanded real path for a given logical path by
120
+ # searching the environment's paths.
121
+ #
122
+ # resolve("application.js")
123
+ # # => "/path/to/app/javascripts/application.js.coffee"
124
+ #
125
+ # A `FileNotFound` exception is raised if the file does not exist.
126
+ def resolve(logical_path, options = {})
127
+ # If a block is given, preform an iterable search
128
+ if block_given?
129
+ args = attributes_for(logical_path).search_paths + [options]
130
+ @trail.find(*args) do |path|
131
+ pathname = Pathname.new(path)
132
+ if %w( .bower.json bower.json component.json ).include?(pathname.basename.to_s)
133
+ bower = json_decode(pathname.read)
134
+ case bower['main']
135
+ when String
136
+ yield pathname.dirname.join(bower['main'])
137
+ when Array
138
+ extname = File.extname(logical_path)
139
+ bower['main'].each do |fn|
140
+ if extname == "" || extname == File.extname(fn)
141
+ yield pathname.dirname.join(fn)
142
+ end
143
+ end
144
+ end
145
+ else
146
+ yield pathname
147
+ end
148
+ end
149
+ else
150
+ resolve(logical_path, options) do |pathname|
151
+ return pathname
152
+ end
153
+ raise FileNotFound, "couldn't find file '#{logical_path}'"
154
+ end
155
+ end
156
+
157
+ # Register a new mime type.
158
+ def register_mime_type(mime_type, ext)
159
+ # Overrides the global behavior to expire the index
160
+ expire_index!
161
+ @trail.append_extension(ext)
162
+ super
163
+ end
164
+
165
+ # Registers a new Engine `klass` for `ext`.
166
+ def register_engine(ext, klass)
167
+ # Overrides the global behavior to expire the index
168
+ expire_index!
169
+ add_engine_to_trail(ext, klass)
170
+ super
171
+ end
172
+
173
+ def register_preprocessor(mime_type, klass, &block)
174
+ # Overrides the global behavior to expire the index
175
+ expire_index!
176
+ super
177
+ end
178
+
179
+ def unregister_preprocessor(mime_type, klass)
180
+ # Overrides the global behavior to expire the index
181
+ expire_index!
182
+ super
183
+ end
184
+
185
+ def register_postprocessor(mime_type, klass, &block)
186
+ # Overrides the global behavior to expire the index
187
+ expire_index!
188
+ super
189
+ end
190
+
191
+ def unregister_postprocessor(mime_type, klass)
192
+ # Overrides the global behavior to expire the index
193
+ expire_index!
194
+ super
195
+ end
196
+
197
+ def register_bundle_processor(mime_type, klass, &block)
198
+ # Overrides the global behavior to expire the index
199
+ expire_index!
200
+ super
201
+ end
202
+
203
+ def unregister_bundle_processor(mime_type, klass)
204
+ # Overrides the global behavior to expire the index
205
+ expire_index!
206
+ super
207
+ end
208
+
101
209
  # Return an `Index`. Must be implemented by the subclass.
102
210
  def index
103
211
  raise NotImplementedError
@@ -113,14 +221,14 @@ module Sprockets
113
221
  #
114
222
  # Subclasses may cache this method.
115
223
  def entries(pathname)
116
- trail.entries(pathname)
224
+ @trail.entries(pathname)
117
225
  end
118
226
 
119
227
  # Works like `File.stat`.
120
228
  #
121
229
  # Subclasses may cache this method.
122
230
  def stat(path)
123
- trail.stat(path)
231
+ @trail.stat(path)
124
232
  end
125
233
 
126
234
  # Read and compute digest of filename.
@@ -153,7 +261,7 @@ module Sprockets
153
261
  # Find asset by logical path or expanded path.
154
262
  def find_asset(path, options = {})
155
263
  logical_path = path
156
- pathname = Pathname.new(path)
264
+ pathname = Pathname.new(path).cleanpath
157
265
 
158
266
  if pathname.absolute?
159
267
  return unless stat(pathname)
@@ -161,6 +269,16 @@ module Sprockets
161
269
  else
162
270
  begin
163
271
  pathname = resolve(logical_path)
272
+
273
+ # If logical path is missing a mime type extension, append
274
+ # the absolute path extname so it has one.
275
+ #
276
+ # Ensures some consistency between finding "foo/bar" vs
277
+ # "foo/bar.js".
278
+ if File.extname(logical_path) == ""
279
+ expanded_logical_path = attributes_for(pathname).logical_path
280
+ logical_path += File.extname(expanded_logical_path)
281
+ end
164
282
  rescue FileNotFound
165
283
  return nil
166
284
  end
@@ -210,13 +328,20 @@ module Sprockets
210
328
  nil
211
329
  end
212
330
 
213
- def each_logical_path(*args)
331
+ def each_logical_path(*args, &block)
214
332
  return to_enum(__method__, *args) unless block_given?
215
333
  filters = args.flatten
216
334
  files = {}
217
335
  each_file do |filename|
218
336
  if logical_path = logical_path_for_filename(filename, filters)
219
- yield logical_path unless files[logical_path]
337
+ unless files[logical_path]
338
+ if block.arity == 2
339
+ yield logical_path, filename.to_s
340
+ else
341
+ yield logical_path
342
+ end
343
+ end
344
+
220
345
  files[logical_path] = true
221
346
  end
222
347
  end
@@ -275,14 +400,14 @@ module Sprockets
275
400
  def logical_path_for_filename(filename, filters)
276
401
  logical_path = attributes_for(filename).logical_path.to_s
277
402
 
278
- if matches_filter(filters, logical_path)
403
+ if matches_filter(filters, logical_path, filename)
279
404
  return logical_path
280
405
  end
281
406
 
282
407
  # If filename is an index file, retest with alias
283
408
  if File.basename(logical_path)[/[^\.]+/, 0] == 'index'
284
409
  path = logical_path.sub(/\/index\./, '.')
285
- if matches_filter(filters, path)
410
+ if matches_filter(filters, path, filename)
286
411
  return path
287
412
  end
288
413
  end
@@ -290,18 +415,33 @@ module Sprockets
290
415
  nil
291
416
  end
292
417
 
293
- def matches_filter(filters, filename)
418
+ def matches_filter(filters, logical_path, filename)
294
419
  return true if filters.empty?
295
420
 
296
421
  filters.any? do |filter|
297
422
  if filter.is_a?(Regexp)
298
- filter.match(filename)
423
+ filter.match(logical_path)
299
424
  elsif filter.respond_to?(:call)
300
- filter.call(filename)
425
+ if filter.arity == 1
426
+ filter.call(logical_path)
427
+ else
428
+ filter.call(logical_path, filename.to_s)
429
+ end
301
430
  else
302
- File.fnmatch(filter.to_s, filename)
431
+ File.fnmatch(filter.to_s, logical_path)
303
432
  end
304
433
  end
305
434
  end
435
+
436
+ # Feature detect newer MultiJson API
437
+ if MultiJson.respond_to?(:dump)
438
+ def json_decode(obj)
439
+ MultiJson.load(obj)
440
+ end
441
+ else
442
+ def json_decode(obj)
443
+ MultiJson.decode(obj)
444
+ end
445
+ end
306
446
  end
307
447
  end
@@ -13,20 +13,19 @@ module Sprockets
13
13
  def initialize(environment, logical_path, pathname)
14
14
  super(environment, logical_path, pathname)
15
15
 
16
- @processed_asset = environment.find_asset(pathname, :bundle => false)
17
- @required_assets = @processed_asset.required_assets
18
-
19
- @source = ""
16
+ @processed_asset = environment.find_asset(pathname, :bundle => false)
17
+ @required_assets = @processed_asset.required_assets
18
+ @dependency_paths = @processed_asset.dependency_paths
20
19
 
21
20
  # Explode Asset into parts and gather the dependency bodies
22
- to_a.each { |dependency| @source << dependency.to_s }
21
+ @source = to_a.map { |dependency| dependency.to_s }.join
23
22
 
24
23
  # Run bundle processors on concatenated source
25
24
  context = environment.context_class.new(environment, logical_path, pathname)
26
25
  @source = context.evaluate(pathname, :data => @source,
27
26
  :processors => environment.bundle_processors(content_type))
28
27
 
29
- @mtime = to_a.map(&:mtime).max
28
+ @mtime = (to_a + @dependency_paths).map(&:mtime).max
30
29
  @length = Rack::Utils.bytesize(source)
31
30
  @digest = environment.digest.update(source).hexdigest
32
31
  end
@@ -2,6 +2,45 @@ module Sprockets
2
2
  # `Caching` is an internal mixin whose public methods are exposed on
3
3
  # the `Environment` and `Index` classes.
4
4
  module Caching
5
+ # Low level cache getter for `key`. Checks a number of supported
6
+ # cache interfaces.
7
+ def cache_get(key)
8
+ # `Cache#get(key)` for Memcache
9
+ if cache.respond_to?(:get)
10
+ cache.get(key)
11
+
12
+ # `Cache#[key]` so `Hash` can be used
13
+ elsif cache.respond_to?(:[])
14
+ cache[key]
15
+
16
+ # `Cache#read(key)` for `ActiveSupport::Cache` support
17
+ elsif cache.respond_to?(:read)
18
+ cache.read(key)
19
+
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ # Low level cache setter for `key`. Checks a number of supported
26
+ # cache interfaces.
27
+ def cache_set(key, value)
28
+ # `Cache#set(key, value)` for Memcache
29
+ if cache.respond_to?(:set)
30
+ cache.set(key, value)
31
+
32
+ # `Cache#[key]=value` so `Hash` can be used
33
+ elsif cache.respond_to?(:[]=)
34
+ cache[key] = value
35
+
36
+ # `Cache#write(key, value)` for `ActiveSupport::Cache` support
37
+ elsif cache.respond_to?(:write)
38
+ cache.write(key, value)
39
+ end
40
+
41
+ value
42
+ end
43
+
5
44
  protected
6
45
  # Cache helper method. Takes a `path` argument which maybe a
7
46
  # logical path or fully expanded path. The `&block` is passed
@@ -53,44 +92,5 @@ module Sprockets
53
92
  cache_set(expand_cache_key(key), hash)
54
93
  hash
55
94
  end
56
-
57
- # Low level cache getter for `key`. Checks a number of supported
58
- # cache interfaces.
59
- def cache_get(key)
60
- # `Cache#get(key)` for Memcache
61
- if cache.respond_to?(:get)
62
- cache.get(key)
63
-
64
- # `Cache#[key]` so `Hash` can be used
65
- elsif cache.respond_to?(:[])
66
- cache[key]
67
-
68
- # `Cache#read(key)` for `ActiveSupport::Cache` support
69
- elsif cache.respond_to?(:read)
70
- cache.read(key)
71
-
72
- else
73
- nil
74
- end
75
- end
76
-
77
- # Low level cache setter for `key`. Checks a number of supported
78
- # cache interfaces.
79
- def cache_set(key, value)
80
- # `Cache#set(key, value)` for Memcache
81
- if cache.respond_to?(:set)
82
- cache.set(key, value)
83
-
84
- # `Cache#[key]=value` so `Hash` can be used
85
- elsif cache.respond_to?(:[]=)
86
- cache[key] = value
87
-
88
- # `Cache#write(key, value)` for `ActiveSupport::Cache` support
89
- elsif cache.respond_to?(:write)
90
- cache.write(key, value)
91
- end
92
-
93
- value
94
- end
95
95
  end
96
96
  end
@@ -0,0 +1,22 @@
1
+ require 'tilt'
2
+
3
+ module Sprockets
4
+ class ClosureCompressor < Tilt::Template
5
+ self.default_mime_type = 'application/javascript'
6
+
7
+ def self.engine_initialized?
8
+ defined?(::Closure::Compiler)
9
+ end
10
+
11
+ def initialize_engine
12
+ require_template_library 'closure-compiler'
13
+ end
14
+
15
+ def prepare
16
+ end
17
+
18
+ def evaluate(context, locals, &block)
19
+ Closure::Compiler.new.compile(data)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,73 @@
1
+ module Sprockets
2
+ # `Compressing` is an internal mixin whose public methods are exposed on
3
+ # the `Environment` and `Index` classes.
4
+ module Compressing
5
+ def compressors
6
+ deep_copy_hash(@compressors)
7
+ end
8
+
9
+ def register_compressor(mime_type, sym, klass)
10
+ @compressors[mime_type][sym] = klass
11
+ end
12
+
13
+ # Return CSS compressor or nil if none is set
14
+ def css_compressor
15
+ @css_compressor if defined? @css_compressor
16
+ end
17
+
18
+ # Assign a compressor to run on `text/css` assets.
19
+ #
20
+ # The compressor object must respond to `compress`.
21
+ def css_compressor=(compressor)
22
+ unregister_bundle_processor 'text/css', css_compressor if css_compressor
23
+ @css_compressor = nil
24
+ return unless compressor
25
+
26
+ if compressor.is_a?(Symbol)
27
+ compressor = compressors['text/css'][compressor] || raise(Error, "unknown compressor: #{compressor}")
28
+ end
29
+
30
+ if compressor.respond_to?(:compress)
31
+ klass = Class.new(Processor) do
32
+ @name = "css_compressor"
33
+ @processor = proc { |context, data| compressor.compress(data) }
34
+ end
35
+ @css_compressor = :css_compressor
36
+ else
37
+ @css_compressor = klass = compressor
38
+ end
39
+
40
+ register_bundle_processor 'text/css', klass
41
+ end
42
+
43
+ # Return JS compressor or nil if none is set
44
+ def js_compressor
45
+ @js_compressor if defined? @js_compressor
46
+ end
47
+
48
+ # Assign a compressor to run on `application/javascript` assets.
49
+ #
50
+ # The compressor object must respond to `compress`.
51
+ def js_compressor=(compressor)
52
+ unregister_bundle_processor 'application/javascript', js_compressor if js_compressor
53
+ @js_compressor = nil
54
+ return unless compressor
55
+
56
+ if compressor.is_a?(Symbol)
57
+ compressor = compressors['application/javascript'][compressor] || raise(Error, "unknown compressor: #{compressor}")
58
+ end
59
+
60
+ if compressor.respond_to?(:compress)
61
+ klass = Class.new(Processor) do
62
+ @name = "js_compressor"
63
+ @processor = proc { |context, data| compressor.compress(data) }
64
+ end
65
+ @js_compressor = :js_compressor
66
+ else
67
+ @js_compressor = klass = compressor
68
+ end
69
+
70
+ register_bundle_processor 'application/javascript', klass
71
+ end
72
+ end
73
+ end