sprockets 3.0.0.beta.6 → 3.0.0.beta.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +171 -100
  3. data/lib/rake/sprocketstask.rb +2 -2
  4. data/lib/sprockets.rb +69 -63
  5. data/lib/sprockets/asset.rb +2 -61
  6. data/lib/sprockets/autoload_processor.rb +48 -0
  7. data/lib/sprockets/base.rb +4 -6
  8. data/lib/sprockets/bower.rb +8 -5
  9. data/lib/sprockets/bundle.rb +9 -13
  10. data/lib/sprockets/cache.rb +19 -14
  11. data/lib/sprockets/cache/file_store.rb +2 -1
  12. data/lib/sprockets/cached_environment.rb +15 -68
  13. data/lib/sprockets/closure_compressor.rb +17 -4
  14. data/lib/sprockets/coffee_script_processor.rb +26 -0
  15. data/lib/sprockets/coffee_script_template.rb +3 -20
  16. data/lib/sprockets/compressing.rb +10 -4
  17. data/lib/sprockets/configuration.rb +21 -37
  18. data/lib/sprockets/context.rb +37 -67
  19. data/lib/sprockets/dependencies.rb +73 -0
  20. data/lib/sprockets/digest_utils.rb +8 -2
  21. data/lib/sprockets/directive_processor.rb +122 -165
  22. data/lib/sprockets/eco_processor.rb +32 -0
  23. data/lib/sprockets/eco_template.rb +3 -26
  24. data/lib/sprockets/ejs_processor.rb +31 -0
  25. data/lib/sprockets/ejs_template.rb +3 -25
  26. data/lib/sprockets/encoding_utils.rb +9 -21
  27. data/lib/sprockets/engines.rb +25 -27
  28. data/lib/sprockets/environment.rb +9 -1
  29. data/lib/sprockets/erb_processor.rb +30 -0
  30. data/lib/sprockets/erb_template.rb +3 -20
  31. data/lib/sprockets/file_reader.rb +15 -0
  32. data/lib/sprockets/http_utils.rb +2 -0
  33. data/lib/sprockets/jst_processor.rb +9 -2
  34. data/lib/sprockets/legacy.rb +212 -3
  35. data/lib/sprockets/legacy_tilt_processor.rb +1 -1
  36. data/lib/sprockets/loader.rb +95 -89
  37. data/lib/sprockets/manifest.rb +23 -59
  38. data/lib/sprockets/mime.rb +28 -41
  39. data/lib/sprockets/path_dependency_utils.rb +76 -0
  40. data/lib/sprockets/path_utils.rb +21 -1
  41. data/lib/sprockets/paths.rb +23 -8
  42. data/lib/sprockets/processing.rb +102 -91
  43. data/lib/sprockets/processor_utils.rb +97 -0
  44. data/lib/sprockets/resolve.rb +110 -97
  45. data/lib/sprockets/sass_cache_store.rb +2 -2
  46. data/lib/sprockets/sass_compressor.rb +17 -4
  47. data/lib/sprockets/sass_functions.rb +2 -2
  48. data/lib/sprockets/sass_importer.rb +2 -2
  49. data/lib/sprockets/sass_processor.rb +305 -0
  50. data/lib/sprockets/sass_template.rb +4 -286
  51. data/lib/sprockets/server.rb +1 -13
  52. data/lib/sprockets/transformers.rb +62 -25
  53. data/lib/sprockets/uglifier_compressor.rb +17 -4
  54. data/lib/sprockets/uri_utils.rb +190 -0
  55. data/lib/sprockets/utils.rb +87 -6
  56. data/lib/sprockets/version.rb +1 -1
  57. data/lib/sprockets/yui_compressor.rb +17 -4
  58. metadata +14 -5
  59. data/lib/sprockets/asset_uri.rb +0 -80
  60. data/lib/sprockets/lazy_processor.rb +0 -15
@@ -0,0 +1,73 @@
1
+ require 'sprockets/digest_utils'
2
+ require 'sprockets/path_digest_utils'
3
+ require 'sprockets/uri_utils'
4
+
5
+ module Sprockets
6
+ # `Dependencies` is an internal mixin whose public methods are exposed on the
7
+ # `Environment` and `CachedEnvironment` classes.
8
+ module Dependencies
9
+ include DigestUtils, PathDigestUtils, URIUtils
10
+
11
+ # Public: Mapping dependency schemes to resolver functions.
12
+ #
13
+ # key - String scheme
14
+ # value - Proc.call(Environment, String)
15
+ #
16
+ # Returns Hash.
17
+ def dependency_resolvers
18
+ config[:dependency_resolvers]
19
+ end
20
+
21
+ # Public: Default set of dependency URIs for assets.
22
+ #
23
+ # Returns Set of String URIs.
24
+ def dependencies
25
+ config[:dependencies]
26
+ end
27
+
28
+ # Public: Register new dependency URI resolver.
29
+ #
30
+ # scheme - String scheme
31
+ # block -
32
+ # environment - Environment
33
+ # uri - String dependency URI
34
+ #
35
+ # Returns nothing.
36
+ def register_dependency_resolver(scheme, &block)
37
+ self.config = hash_reassoc(config, :dependency_resolvers) do |hash|
38
+ hash.merge(scheme => block)
39
+ end
40
+ end
41
+
42
+ # Public: Add environmental dependency inheirted by all assets.
43
+ #
44
+ # uri - String dependency URI
45
+ #
46
+ # Returns nothing.
47
+ def add_dependency(uri)
48
+ self.config = hash_reassoc(config, :dependencies) do |set|
49
+ set + Set.new([uri])
50
+ end
51
+ end
52
+ alias_method :depend_on, :add_dependency
53
+
54
+ # Internal: Resolve set of dependency URIs.
55
+ #
56
+ # Returns Array of resolved Objects.
57
+ def resolve_dependencies(uris)
58
+ uris.map { |uri| resolve_dependency(uri) }
59
+ end
60
+
61
+ # Internal: Resolve dependency URIs.
62
+ #
63
+ # Returns resolved Object.
64
+ def resolve_dependency(str)
65
+ scheme = str[/([^:]+)/, 1]
66
+ if resolver = dependency_resolvers[scheme]
67
+ resolver.call(self, str)
68
+ else
69
+ raise TypeError, "unknown cache scheme: #{scheme}"
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,8 +1,11 @@
1
1
  require 'digest/md5'
2
2
  require 'digest/sha1'
3
3
  require 'digest/sha2'
4
+ require 'set'
4
5
 
5
6
  module Sprockets
7
+ # Internal: Hash functions and digest related utilities. Mixed into
8
+ # Environment.
6
9
  module DigestUtils
7
10
  extend self
8
11
 
@@ -33,6 +36,9 @@ module Sprockets
33
36
 
34
37
  # Internal: Generate a hexdigest for a nested JSON serializable object.
35
38
  #
39
+ # This is used for generating cache keys, so its pretty important its
40
+ # wicked fast. Microbenchmarks away!
41
+ #
36
42
  # obj - A JSON serializable object.
37
43
  #
38
44
  # Returns a String digest of the object.
@@ -111,7 +117,7 @@ module Sprockets
111
117
  # Internal: Maps digest class to the named information hash algorithm name.
112
118
  #
113
119
  # http://www.iana.org/assignments/named-information/named-information.xhtml
114
- NI_HASH_ALGORIHMS = {
120
+ NI_HASH_ALGORITHMS = {
115
121
  Digest::SHA256 => 'sha-256'.freeze,
116
122
  Digest::SHA384 => 'sha-384'.freeze,
117
123
  Digest::SHA512 => 'sha-512'.freeze
@@ -137,7 +143,7 @@ module Sprockets
137
143
  raise TypeError, "unknown digest: #{digest.inspect}"
138
144
  end
139
145
 
140
- if hash_name = NI_HASH_ALGORIHMS[digest_class]
146
+ if hash_name = NI_HASH_ALGORITHMS[digest_class]
141
147
  uri = "ni:///#{hash_name};#{pack_urlsafe_base64digest(digest)}"
142
148
  uri << "?ct=#{content_type}" if content_type
143
149
  uri
@@ -36,24 +36,6 @@ module Sprockets
36
36
  class DirectiveProcessor
37
37
  VERSION = '1'
38
38
 
39
- # Directives will only be picked up if they are in the header
40
- # of the source file. C style (/* */), JavaScript (//), and
41
- # Ruby (#) comments are supported.
42
- #
43
- # Directives in comments after the first non-whitespace line
44
- # of code will not be processed.
45
- #
46
- HEADER_PATTERN = /
47
- \A (
48
- (?m:\s*) (
49
- (\/\* (?m:.*?) \*\/) |
50
- (\#\#\# (?m:.*?) \#\#\#) |
51
- (\/\/ .* \n?)+ |
52
- (\# .* \n?)+
53
- )
54
- )+
55
- /x
56
-
57
39
  # Directives are denoted by a `=` followed by the name, then
58
40
  # argument list.
59
41
  #
@@ -67,40 +49,69 @@ module Sprockets
67
49
  ^ \W* = \s* (\w+.*?) (\*\/)? $
68
50
  /x
69
51
 
52
+ def self.instance
53
+ @instance ||= new(
54
+ # Deprecated: Default to C and Ruby comment styles
55
+ comments: ["//", ["/*", "*/"]] + ["#", ["###", "###"]]
56
+ )
57
+ end
58
+
70
59
  def self.call(input)
71
- new.call(input)
60
+ instance.call(input)
61
+ end
62
+
63
+ def initialize(options = {})
64
+ @header_pattern = compile_header_pattern(Array(options[:comments]))
72
65
  end
73
66
 
74
67
  def call(input)
68
+ dup._call(input)
69
+ end
70
+
71
+ def _call(input)
75
72
  @environment = input[:environment]
76
73
  @uri = input[:uri]
77
- @load_path = input[:load_path]
78
74
  @filename = input[:filename]
79
75
  @dirname = File.dirname(@filename)
80
76
  @content_type = input[:content_type]
77
+ @required = Set.new(input[:metadata][:required])
78
+ @stubbed = Set.new(input[:metadata][:stubbed])
79
+ @links = Set.new(input[:metadata][:links])
80
+ @dependencies = Set.new(input[:metadata][:dependencies])
81
81
 
82
- data = input[:data]
83
- result = process_source(data)
84
-
85
- data, directives = result.values_at(:data, :directives)
86
-
87
- @required = Set.new(input[:metadata][:required])
88
- @stubbed = Set.new(input[:metadata][:stubbed])
89
- @links = Set.new(input[:metadata][:links])
90
- @dependency_paths = Set.new(input[:metadata][:dependency_paths])
91
-
82
+ data, directives = process_source(input[:data])
92
83
  process_directives(directives)
93
84
 
94
85
  { data: data,
95
86
  required: @required,
96
87
  stubbed: @stubbed,
97
88
  links: @links,
98
- dependency_paths: @dependency_paths }
89
+ dependencies: @dependencies }
99
90
  end
100
91
 
101
92
  protected
93
+ # Directives will only be picked up if they are in the header
94
+ # of the source file. C style (/* */), JavaScript (//), and
95
+ # Ruby (#) comments are supported.
96
+ #
97
+ # Directives in comments after the first non-whitespace line
98
+ # of code will not be processed.
99
+ def compile_header_pattern(comments)
100
+ re = comments.map { |c|
101
+ case c
102
+ when String
103
+ "(?:#{Regexp.escape(c)}.*\\n?)+"
104
+ when Array
105
+ "(?:#{Regexp.escape(c[0])}(?m:.*?)#{Regexp.escape(c[1])})"
106
+ else
107
+ raise TypeError, "unknown comment type: #{c.class}"
108
+ end
109
+ }.join("|")
110
+ Regexp.compile("\\A(?:(?m:\\s*)(?:#{re}))+")
111
+ end
112
+
102
113
  def process_source(source)
103
- header = source[HEADER_PATTERN, 0] || ""
114
+ header = source[@header_pattern, 0] || ""
104
115
  body = $' || source
105
116
 
106
117
  header, directives = extract_directives(header)
@@ -112,7 +123,7 @@ module Sprockets
112
123
  # Ensure body ends in a new line
113
124
  data << "\n" if data.length > 0 && data[-1] != "\n"
114
125
 
115
- { data: data, directives: directives }
126
+ return data, directives
116
127
  end
117
128
 
118
129
  # Returns an Array of directive structures. Each structure
@@ -193,7 +204,7 @@ module Sprockets
193
204
  # //= require "./bar"
194
205
  #
195
206
  def process_require_directive(path)
196
- @required << locate(path, accept: @content_type, bundle: false)
207
+ @required << resolve(path, accept: @content_type, bundle: false)
197
208
  end
198
209
 
199
210
  # `require_self` causes the body of the current file to be inserted
@@ -220,28 +231,8 @@ module Sprockets
220
231
  # //= require_directory "./javascripts"
221
232
  #
222
233
  def process_require_directory_directive(path = ".")
223
- if @environment.relative_path?(path)
224
- root = expand_relative_path(path)
225
-
226
- unless (stats = @environment.stat(root)) && stats.directory?
227
- raise ArgumentError, "require_directory argument must be a directory"
228
- end
229
-
230
- @dependency_paths << root
231
-
232
- @environment.stat_directory(root).each do |subpath, stat|
233
- if subpath == @filename
234
- next
235
- elsif stat.directory?
236
- next
237
- elsif uri = @environment.locate(subpath, accept: @content_type, bundle: false)
238
- @required << uri
239
- end
240
- end
241
- else
242
- # The path must be relative and start with a `./`.
243
- raise ArgumentError, "require_directory argument must be a relative path"
244
- end
234
+ path = expand_relative_dirname(:require_directory, path)
235
+ require_paths(*@environment.stat_directory_with_dependencies(path))
245
236
  end
246
237
 
247
238
  # `require_tree` requires all the nested files in a directory.
@@ -250,28 +241,8 @@ module Sprockets
250
241
  # //= require_tree "./public"
251
242
  #
252
243
  def process_require_tree_directive(path = ".")
253
- if @environment.relative_path?(path)
254
- root = expand_relative_path(path)
255
-
256
- unless (stats = @environment.stat(root)) && stats.directory?
257
- raise ArgumentError, "require_tree argument must be a directory"
258
- end
259
-
260
- @dependency_paths << root
261
-
262
- @environment.stat_sorted_tree(root).each do |subpath, stat|
263
- if subpath == @filename
264
- next
265
- elsif stat.directory?
266
- @dependency_paths << subpath
267
- elsif uri = @environment.locate(subpath, accept: @content_type, bundle: false)
268
- @required << uri
269
- end
270
- end
271
- else
272
- # The path must be relative and start with a `./`.
273
- raise ArgumentError, "require_tree argument must be a relative path"
274
- end
244
+ path = expand_relative_dirname(:require_tree, path)
245
+ require_paths(*@environment.stat_sorted_tree_with_dependencies(path))
275
246
  end
276
247
 
277
248
  # Allows you to state a dependency on a file without
@@ -287,7 +258,7 @@ module Sprockets
287
258
  # //= depend_on "foo.png"
288
259
  #
289
260
  def process_depend_on_directive(path)
290
- @dependency_paths << resolve(path)
261
+ resolve(path)
291
262
  end
292
263
 
293
264
  # Allows you to state a dependency on an asset without including
@@ -302,9 +273,7 @@ module Sprockets
302
273
  # //= depend_on_asset "bar.js"
303
274
  #
304
275
  def process_depend_on_asset_directive(path)
305
- if asset = @environment.load(locate(path))
306
- @dependency_paths.merge(asset.metadata[:dependency_paths])
307
- end
276
+ load(resolve(path))
308
277
  end
309
278
 
310
279
  # Allows dependency to be excluded from the asset bundle.
@@ -316,7 +285,7 @@ module Sprockets
316
285
  # //= stub "jquery"
317
286
  #
318
287
  def process_stub_directive(path)
319
- @stubbed << locate(path, accept: @content_type, bundle: false)
288
+ @stubbed << resolve(path, accept: @content_type, bundle: false)
320
289
  end
321
290
 
322
291
  # Declares a linked dependency on the target asset.
@@ -328,10 +297,7 @@ module Sprockets
328
297
  # /*= link "logo.png" */
329
298
  #
330
299
  def process_link_directive(path)
331
- if asset = @environment.load(locate(path))
332
- @dependency_paths.merge(asset.metadata[:dependency_paths])
333
- @links << asset.uri
334
- end
300
+ @links << load(resolve(path)).uri
335
301
  end
336
302
 
337
303
  # `link_directory` links all the files inside a single
@@ -340,31 +306,15 @@ module Sprockets
340
306
  #
341
307
  # //= link_directory "./fonts"
342
308
  #
343
- def process_link_directory_directive(path = ".")
344
- if @environment.relative_path?(path)
345
- root = expand_relative_path(path)
346
-
347
- unless (stats = @environment.stat(root)) && stats.directory?
348
- raise ArgumentError, "link_directory argument must be a directory"
349
- end
350
-
351
- @dependency_paths << root
352
-
353
- @environment.stat_directory(root).each do |subpath, stat|
354
- if subpath == @filename
355
- next
356
- elsif stat.directory?
357
- next
358
- elsif uri = @environment.locate(subpath)
359
- asset = @environment.load(uri)
360
- @dependency_paths.merge(asset.metadata[:dependency_paths])
361
- @links << asset.uri
362
- end
363
- end
364
- else
365
- # The path must be relative and start with a `./`.
366
- raise ArgumentError, "link_directory argument must be a relative path"
367
- end
309
+ # Use caution when linking against JS or CSS assets. Include an explicit
310
+ # extension or content type in these cases
311
+ #
312
+ # //= link_directory "./scripts" .js
313
+ #
314
+ def process_link_directory_directive(path = ".", accept = nil)
315
+ path = expand_relative_dirname(:link_directory, path)
316
+ accept = expand_accept_shorthand(accept)
317
+ link_paths(*@environment.stat_directory_with_dependencies(path), accept)
368
318
  end
369
319
 
370
320
  # `link_tree` links all the nested files in a directory.
@@ -372,76 +322,83 @@ module Sprockets
372
322
  #
373
323
  # //= link_tree "./images"
374
324
  #
375
- def process_link_tree_directive(path = ".")
376
- if @environment.relative_path?(path)
377
- root = expand_relative_path(path)
378
-
379
- unless (stats = @environment.stat(root)) && stats.directory?
380
- raise ArgumentError, "link_tree argument must be a directory"
381
- end
325
+ # Use caution when linking against JS or CSS assets. Include an explicit
326
+ # extension or content type in these cases
327
+ #
328
+ # //= link_tree "./styles" .css
329
+ #
330
+ def process_link_tree_directive(path = ".", accept = nil)
331
+ path = expand_relative_dirname(:link_tree, path)
332
+ accept = expand_accept_shorthand(accept)
333
+ link_paths(*@environment.stat_sorted_tree_with_dependencies(path), accept)
334
+ end
382
335
 
383
- @dependency_paths << root
384
-
385
- @environment.stat_sorted_tree(root).each do |subpath, stat|
386
- if subpath == @filename
387
- next
388
- elsif stat.directory?
389
- @dependency_paths << subpath
390
- elsif uri = @environment.locate(subpath)
391
- asset = @environment.load(uri)
392
- @dependency_paths.merge(asset.metadata[:dependency_paths])
393
- @links << asset.uri
394
- end
395
- end
336
+ private
337
+ def expand_accept_shorthand(accept)
338
+ if accept.nil?
339
+ nil
340
+ elsif accept.include?("/")
341
+ accept
342
+ elsif accept.start_with?(".")
343
+ @environment.mime_exts[accept]
396
344
  else
397
- # The path must be relative and start with a `./`.
398
- raise ArgumentError, "link_tree argument must be a relative path"
345
+ @environment.mime_exts[".#{accept}"]
399
346
  end
400
347
  end
401
348
 
402
- private
403
- def expand_relative_path(path)
404
- File.expand_path(path, @dirname)
349
+ def require_paths(paths, deps)
350
+ resolve_paths(paths, deps, accept: @content_type, bundle: false) do |uri|
351
+ @required << uri
352
+ end
405
353
  end
406
354
 
407
- def locate(path, options = {})
408
- _resolve(:locate, path, options)
355
+ def link_paths(paths, deps, accept)
356
+ resolve_paths(paths, deps, accept: accept) do |uri|
357
+ @links << load(uri).uri
358
+ end
409
359
  end
410
360
 
411
- def resolve(path, options = {})
412
- _resolve(:resolve, path, options)
361
+ def resolve_paths(paths, deps, options = {})
362
+ @dependencies.merge(deps)
363
+ paths.each do |subpath, stat|
364
+ next if subpath == @filename || stat.directory?
365
+ uri, deps = @environment.resolve(subpath, options.merge(compat: false))
366
+ @dependencies.merge(deps)
367
+ yield uri if uri
368
+ end
413
369
  end
414
370
 
415
- # TODO: Cleanup relative resolver logic shared between context.
416
- def _resolve(method, path, options = {})
417
- if @environment.absolute_path?(path)
418
- raise FileOutsidePaths, "can't require absolute file: #{path}"
419
- elsif @environment.relative_path?(path)
420
- path = expand_relative_path(path)
421
- if logical_path = @environment.split_subpath(@load_path, path)
422
- if filename = @environment.send(method, logical_path, options.merge(load_paths: [@load_path]))
423
- filename
424
- else
425
- accept = options[:accept]
426
- message = "couldn't find file '#{logical_path}' under '#{@load_path}'"
427
- message << " with type '#{accept}'" if accept
428
- raise FileNotFound, message
429
- end
371
+ def expand_relative_dirname(directive, path)
372
+ if @environment.relative_path?(path)
373
+ path = File.expand_path(path, @dirname)
374
+ stat = @environment.stat(path)
375
+
376
+ if stat && stat.directory?
377
+ path
430
378
  else
431
- raise FileOutsidePaths, "#{path} isn't under path: #{@load_path}"
379
+ raise ArgumentError, "#{directive} argument must be a directory"
432
380
  end
433
381
  else
434
- filename = @environment.send(method, path, options)
382
+ # The path must be relative and start with a `./`.
383
+ raise ArgumentError, "#{directive} argument must be a relative path"
435
384
  end
385
+ end
436
386
 
437
- if filename
438
- filename
439
- else
440
- accept = options[:accept]
441
- message = "couldn't find file '#{path}'"
442
- message << " with type '#{accept}'" if accept
443
- raise FileNotFound, message
387
+ def load(uri)
388
+ asset = @environment.load(uri)
389
+ @dependencies.merge(asset.metadata[:dependencies])
390
+ asset
391
+ end
392
+
393
+ def resolve(path, options = {})
394
+ # Prevent absolute paths in directives
395
+ if @environment.absolute_path?(path)
396
+ raise FileOutsidePaths, "can't require absolute file: #{path}"
444
397
  end
398
+
399
+ uri, deps = @environment.resolve!(path, options.merge(base_path: @dirname))
400
+ @dependencies.merge(deps)
401
+ uri
445
402
  end
446
403
  end
447
404
  end