sprockets 2.2.3 → 2.12.2

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.

@@ -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
@@ -53,7 +53,7 @@ module Sprockets
53
53
  # # => 'application'
54
54
  #
55
55
  def logical_path
56
- @logical_path[/^([^.]+)/, 0]
56
+ @logical_path.chomp(File.extname(@logical_path))
57
57
  end
58
58
 
59
59
  # Returns content type of file
@@ -81,7 +81,11 @@ module Sprockets
81
81
  attributes = environment.attributes_for(pathname)
82
82
 
83
83
  if pathname.absolute?
84
- pathname
84
+ if environment.stat(pathname)
85
+ pathname
86
+ else
87
+ raise FileNotFound, "couldn't find file '#{pathname}'"
88
+ end
85
89
 
86
90
  elsif content_type = options[:content_type]
87
91
  content_type = self.content_type if content_type == :self
@@ -101,7 +105,7 @@ module Sprockets
101
105
 
102
106
  raise FileNotFound, "couldn't find file '#{path}'"
103
107
  else
104
- environment.resolve(path, :base_path => self.pathname.dirname, &block)
108
+ environment.resolve(path, {:base_path => self.pathname.dirname}.merge(options), &block)
105
109
  end
106
110
  end
107
111
 
@@ -217,6 +221,56 @@ module Sprockets
217
221
  "data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}"
218
222
  end
219
223
 
224
+ # Expands logical path to full url to asset.
225
+ #
226
+ # NOTE: This helper is currently not implemented and should be
227
+ # customized by the application. Though, in the future, some
228
+ # basics implemention may be provided with different methods that
229
+ # are required to be overridden.
230
+ def asset_path(path, options = {})
231
+ message = <<-EOS
232
+ Custom asset_path helper is not implemented
233
+
234
+ Extend your environment context with a custom method.
235
+
236
+ environment.context_class.class_eval do
237
+ def asset_path(path, options = {})
238
+ end
239
+ end
240
+ EOS
241
+ raise NotImplementedError, message
242
+ end
243
+
244
+ # Expand logical image asset path.
245
+ def image_path(path)
246
+ asset_path(path, :type => :image)
247
+ end
248
+
249
+ # Expand logical video asset path.
250
+ def video_path(path)
251
+ asset_path(path, :type => :video)
252
+ end
253
+
254
+ # Expand logical audio asset path.
255
+ def audio_path(path)
256
+ asset_path(path, :type => :audio)
257
+ end
258
+
259
+ # Expand logical font asset path.
260
+ def font_path(path)
261
+ asset_path(path, :type => :font)
262
+ end
263
+
264
+ # Expand logical javascript asset path.
265
+ def javascript_path(path)
266
+ asset_path(path, :type => :javascript)
267
+ end
268
+
269
+ # Expand logical stylesheet asset path.
270
+ def stylesheet_path(path)
271
+ asset_path(path, :type => :stylesheet)
272
+ end
273
+
220
274
  private
221
275
  # Annotates exception backtrace with the original template that
222
276
  # the exception was raised in.
@@ -65,7 +65,7 @@ module Sprockets
65
65
  # //= require "foo"
66
66
  #
67
67
  DIRECTIVE_PATTERN = /
68
- ^ [\W]* = \s* (\w+.*?) (\*\/)? $
68
+ ^ \W* = \s* (\w+.*?) (\*\/)? $
69
69
  /x
70
70
 
71
71
  attr_reader :pathname
@@ -92,6 +92,8 @@ module Sprockets
92
92
  @context = context
93
93
 
94
94
  @result = ""
95
+ @result.force_encoding(body.encoding) if body.respond_to?(:encoding)
96
+
95
97
  @has_written_body = false
96
98
 
97
99
  process_directives
@@ -28,12 +28,12 @@ module Sprockets
28
28
  # Sprockets.register_engine '.sass', SassTemplate
29
29
  #
30
30
  module Engines
31
- # Returns an `Array` of `Engine`s registered on the
32
- # `Environment`. If an `ext` argument is supplied, the `Engine`
33
- # register under that extension will be returned.
31
+ # Returns a `Hash` of `Engine`s registered on the `Environment`.
32
+ # If an `ext` argument is supplied, the `Engine` associated with
33
+ # that extension will be returned.
34
34
  #
35
35
  # environment.engines
36
- # # => [CoffeeScriptTemplate, SassTemplate, ...]
36
+ # # => {".coffee" => CoffeeScriptTemplate, ".sass" => SassTemplate, ...}
37
37
  #
38
38
  # environment.engines('.coffee')
39
39
  # # => CoffeeScriptTemplate
@@ -1,9 +1,6 @@
1
1
  require 'sprockets/base'
2
- require 'sprockets/charset_normalizer'
3
2
  require 'sprockets/context'
4
- require 'sprockets/directive_processor'
5
3
  require 'sprockets/index'
6
- require 'sprockets/safety_colons'
7
4
 
8
5
  require 'hike'
9
6
  require 'logger'
@@ -35,24 +32,24 @@ module Sprockets
35
32
  @digest_class = ::Digest::MD5
36
33
  @version = ''
37
34
 
38
- @mime_types = {}
35
+ @mime_types = Sprockets.registered_mime_types
39
36
  @engines = Sprockets.engines
40
- @preprocessors = Hash.new { |h, k| h[k] = [] }
41
- @postprocessors = Hash.new { |h, k| h[k] = [] }
42
- @bundle_processors = Hash.new { |h, k| h[k] = [] }
37
+ @preprocessors = Sprockets.preprocessors
38
+ @postprocessors = Sprockets.postprocessors
39
+ @bundle_processors = Sprockets.bundle_processors
40
+ @compressors = Sprockets.compressors
41
+
42
+ Sprockets.paths.each do |path|
43
+ append_path(path)
44
+ end
43
45
 
44
46
  @engines.each do |ext, klass|
45
47
  add_engine_to_trail(ext, klass)
46
48
  end
47
49
 
48
- register_mime_type 'text/css', '.css'
49
- register_mime_type 'application/javascript', '.js'
50
-
51
- register_preprocessor 'text/css', DirectiveProcessor
52
- register_preprocessor 'application/javascript', DirectiveProcessor
53
-
54
- register_postprocessor 'application/javascript', SafetyColons
55
- register_bundle_processor 'text/css', CharsetNormalizer
50
+ @mime_types.each do |ext, type|
51
+ @trail.append_extension(ext)
52
+ end
56
53
 
57
54
  expire_index!
58
55
 
@@ -7,6 +7,7 @@ module Sprockets
7
7
  class EncodingError < Error; end
8
8
  class FileNotFound < Error; end
9
9
  class FileOutsidePaths < Error; end
10
+ class NotImplementedError < Error; end
10
11
  class UnserializeError < Error; end
11
12
 
12
13
  module EngineError
@@ -31,6 +31,7 @@ module Sprockets
31
31
  @preprocessors = environment.preprocessors
32
32
  @postprocessors = environment.postprocessors
33
33
  @bundle_processors = environment.bundle_processors
34
+ @compressors = environment.compressors
34
35
 
35
36
  # Initialize caches
36
37
  @assets = {}
@@ -2,9 +2,7 @@ require 'tilt'
2
2
 
3
3
  module Sprockets
4
4
  class JstProcessor < Tilt::Template
5
- def self.default_mime_type
6
- 'application/javascript'
7
- end
5
+ self.default_mime_type = 'application/javascript'
8
6
 
9
7
  def self.default_namespace
10
8
  'this.JST'
@@ -18,9 +16,7 @@ module Sprockets
18
16
 
19
17
  def evaluate(scope, locals, &block)
20
18
  <<-JST
21
- (function() {
22
- #{namespace} || (#{namespace} = {});
23
- #{namespace}[#{scope.logical_path.inspect}] = #{indent(data)};
19
+ (function() { #{namespace} || (#{namespace} = {}); #{namespace}[#{scope.logical_path.inspect}] = #{indent(data)};
24
20
  }).call(this);
25
21
  JST
26
22
  end
@@ -1,4 +1,5 @@
1
1
  require 'multi_json'
2
+ require 'securerandom'
2
3
  require 'time'
3
4
 
4
5
  module Sprockets
@@ -18,26 +19,50 @@ module Sprockets
18
19
  # a full path to the manifest json file. The file may or may not
19
20
  # already exist. The dirname of the `path` will be used to write
20
21
  # compiled assets to. Otherwise, if the path is a directory, the
21
- # filename will default to "manifest.json" in that directory.
22
+ # filename will default a random "manifest-123.json" file in that
23
+ # directory.
22
24
  #
23
25
  # Manifest.new(environment, "./public/assets/manifest.json")
24
26
  #
25
- def initialize(environment, path)
26
- @environment = environment
27
+ def initialize(*args)
28
+ if args.first.is_a?(Base) || args.first.nil?
29
+ @environment = args.shift
30
+ end
27
31
 
28
- if File.extname(path) == ""
29
- @dir = File.expand_path(path)
30
- @path = File.join(@dir, 'manifest.json')
31
- else
32
- @path = File.expand_path(path)
33
- @dir = File.dirname(path)
32
+ @dir, @path = args[0], args[1]
33
+
34
+ # Expand paths
35
+ @dir = File.expand_path(@dir) if @dir
36
+ @path = File.expand_path(@path) if @path
37
+
38
+ # If path is given as the second arg
39
+ if @dir && File.extname(@dir) != ""
40
+ @dir, @path = nil, @dir
41
+ end
42
+
43
+ # Default dir to the directory of the path
44
+ @dir ||= File.dirname(@path) if @path
45
+
46
+ # If directory is given w/o path, pick a random manifest.json location
47
+ if @dir && @path.nil?
48
+ # Find the first manifest.json in the directory
49
+ paths = Dir[File.join(@dir, "manifest*.json")]
50
+ if paths.any?
51
+ @path = paths.first
52
+ else
53
+ @path = File.join(@dir, "manifest-#{SecureRandom.hex(16)}.json")
54
+ end
55
+ end
56
+
57
+ unless @dir && @path
58
+ raise ArgumentError, "manifest requires output path"
34
59
  end
35
60
 
36
61
  data = nil
37
62
 
38
63
  begin
39
64
  if File.exist?(@path)
40
- data = MultiJson.decode(File.read(@path))
65
+ data = json_decode(File.read(@path))
41
66
  end
42
67
  rescue MultiJson::DecodeError => e
43
68
  logger.error "#{@path} is invalid: #{e.class} #{e.message}"
@@ -83,14 +108,19 @@ module Sprockets
83
108
  # compile("application.js")
84
109
  #
85
110
  def compile(*args)
111
+ unless environment
112
+ raise Error, "manifest requires environment for compilation"
113
+ end
114
+
86
115
  paths = environment.each_logical_path(*args).to_a +
87
- args.flatten.select { |fn| Pathname.new(fn).absolute? }
116
+ args.flatten.select { |fn| Pathname.new(fn).absolute? if fn.is_a?(String)}
88
117
 
89
118
  paths.each do |path|
90
119
  if asset = find_asset(path)
91
120
  files[asset.digest_path] = {
92
121
  'logical_path' => asset.logical_path,
93
122
  'mtime' => asset.mtime.iso8601,
123
+ 'size' => asset.bytesize,
94
124
  'digest' => asset.digest
95
125
  }
96
126
  assets[asset.logical_path] = asset.digest_path
@@ -102,12 +132,13 @@ module Sprockets
102
132
  else
103
133
  logger.info "Writing #{target}"
104
134
  asset.write_to target
135
+ asset.write_to "#{target}.gz" if asset.is_a?(BundledAsset)
105
136
  end
106
137
 
107
- save
108
- asset
109
138
  end
110
139
  end
140
+ save
141
+ paths
111
142
  end
112
143
 
113
144
  # Removes file from directory and from manifest. `filename` must
@@ -117,6 +148,7 @@ module Sprockets
117
148
  #
118
149
  def remove(filename)
119
150
  path = File.join(dir, filename)
151
+ gzip = "#{path}.gz"
120
152
  logical_path = files[filename]['logical_path']
121
153
 
122
154
  if assets[logical_path] == filename
@@ -125,10 +157,11 @@ module Sprockets
125
157
 
126
158
  files.delete(filename)
127
159
  FileUtils.rm(path) if File.exist?(path)
160
+ FileUtils.rm(gzip) if File.exist?(gzip)
128
161
 
129
162
  save
130
163
 
131
- logger.warn "Removed #{filename}"
164
+ logger.info "Removed #{filename}"
132
165
 
133
166
  nil
134
167
  end
@@ -151,7 +184,7 @@ module Sprockets
151
184
  # Wipe directive
152
185
  def clobber
153
186
  FileUtils.rm_r(@dir) if File.exist?(@dir)
154
- logger.warn "Removed #{@dir}"
187
+ logger.info "Removed #{@dir}"
155
188
  nil
156
189
  end
157
190
 
@@ -177,21 +210,46 @@ module Sprockets
177
210
  ms = benchmark do
178
211
  asset = environment.find_asset(logical_path)
179
212
  end
180
- logger.warn "Compiled #{logical_path} (#{ms}ms)"
213
+ logger.debug "Compiled #{logical_path} (#{ms}ms)"
181
214
  asset
182
215
  end
183
216
 
184
217
  # Persist manfiest back to FS
185
218
  def save
186
- FileUtils.mkdir_p dir
219
+ FileUtils.mkdir_p File.dirname(path)
187
220
  File.open(path, 'w') do |f|
188
- f.write MultiJson.encode(@data)
221
+ f.write json_encode(@data)
189
222
  end
190
223
  end
191
224
 
192
225
  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
239
+
240
+ def json_encode(obj)
241
+ MultiJson.encode(obj)
242
+ end
243
+ end
244
+
193
245
  def logger
194
- environment.logger
246
+ if environment
247
+ environment.logger
248
+ else
249
+ logger = Logger.new($stderr)
250
+ logger.level = Logger::FATAL
251
+ logger
252
+ end
195
253
  end
196
254
 
197
255
  def benchmark