sprockets 2.2.3 → 2.12.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.
@@ -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