sprockets 2.1.0 → 2.2.0

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.

data/README.md CHANGED
@@ -354,9 +354,26 @@ submit a pull request.
354
354
 
355
355
  ## Version History ##
356
356
 
357
+ **2.2.0** (Unreleased)
358
+
359
+ * Added `sprockets` command line utility.
360
+ * Added rake/sprocketstask.
361
+ * Added json manifest log of compiled assets.
362
+ * Added `stub` directive that allows you to exclude files from the bundle.
363
+ * Added per environment external encoding (Environment#default_external_encoding). Defaults to UTF-8. Fixes issues where LANG is not set correctly and Rubys default external is set to ASCII.
364
+
365
+ **2.1.2** (November 20, 2011)
366
+
367
+ * Disabled If-Modified-Since server checks. Fixes some browser caching issues when serving the asset body only. If-None-Match caching is sufficent.
368
+
369
+ **2.1.1** (November 18, 2011)
370
+
371
+ * Fix windows absolute path check bug.
372
+
357
373
  **2.1.0** (November 11, 2011)
358
374
 
359
- * Preserve directive whitespace.
375
+ * Directive comment lines are now turned into empty lines instead of removed. This way line numbers in
376
+ CoffeeScript syntax errors are correct.
360
377
  * Performance and caching bug fixes.
361
378
 
362
379
  **2.0.3** (October 17, 2011)
data/bin/sprockets ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sprockets'
4
+ require 'optparse'
5
+ require 'shellwords'
6
+
7
+ unless ARGV.delete("--noenv")
8
+ if File.exist?(path = "./.sprocketsrc")
9
+ rcflags = Shellwords.split(File.read(path))
10
+ ARGV.unshift(*rcflags)
11
+ end
12
+ end
13
+
14
+ filenames = []
15
+ environment = Sprockets::Environment.new(Dir.pwd)
16
+ manifest = nil
17
+
18
+ (ENV['SPROCKETS_PATH'] || "").split(File::PATH_SEPARATOR).each do |path|
19
+ environment.append_path path
20
+ end
21
+
22
+ OptionParser.new do |opts|
23
+ opts.summary_width = 28
24
+ opts.banner = "Usage: sprockets [options] filename [filename ...]"
25
+
26
+ def opts.show_usage
27
+ puts self
28
+ exit 1
29
+ end
30
+
31
+ opts.on("-r", "--require LIBRARY", "Require the LIBRARY before doing anything") do |lib|
32
+ require lib
33
+ end
34
+
35
+ opts.on("-I DIRECTORY", "--include=DIRECTORY", "Adds the directory to the Sprockets load path") do |directory|
36
+ environment.append_path directory
37
+ end
38
+
39
+ opts.on("-o DIRECTORY", "--output=DIRECTORY", "Copy provided assets into DIRECTORY") do |directory|
40
+ manifest = Sprockets::Manifest.new(environment, directory)
41
+ end
42
+
43
+ opts.on("--noenv", "Disables .sprocketsrc file") do
44
+ end
45
+
46
+ opts.on_tail("-h", "--help", "Shows this help message") do
47
+ opts.show_usage
48
+ end
49
+
50
+ opts.on_tail("-v", "--version", "Shows version") do
51
+ puts Sprockets::VERSION
52
+ exit
53
+ end
54
+
55
+ opts.show_usage if ARGV.empty?
56
+
57
+ begin
58
+ opts.order(ARGV) do |filename|
59
+ filenames << File.expand_path(filename)
60
+ end
61
+ rescue OptionParser::ParseError => e
62
+ opts.warn e.message
63
+ opts.show_usage
64
+ end
65
+ end
66
+
67
+ if environment.paths.empty?
68
+ warn "No load paths given"
69
+ warn "Usage: sprockets -Ijavascripts/ filename"
70
+ exit 1
71
+ end
72
+
73
+ if manifest
74
+ manifest.compile(filenames)
75
+ elsif filenames.length == 1
76
+ puts environment.find_asset(filenames.first).to_s
77
+ else
78
+ warn "Only one file can be compiled to stdout at a time"
79
+ exit 1
80
+ end
@@ -0,0 +1,140 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ require 'sprockets'
5
+ require 'logger'
6
+
7
+ module Rake
8
+ # Simple Sprockets compilation Rake task macro.
9
+ #
10
+ # Rake::SprocketsTask.new do |t|
11
+ # t.environment = Sprockets::Environment.new
12
+ # t.output = "./public/assets"
13
+ # t.assets = %w( application.js application.css )
14
+ # end
15
+ #
16
+ class SprocketsTask < Rake::TaskLib
17
+ # Name of the task. Defaults to "assets".
18
+ #
19
+ # The name will also be used to suffix the clean and clobber
20
+ # tasks, "clean_assets" and "clobber_assets".
21
+ attr_accessor :name
22
+
23
+ # `Environment` instance used for finding assets.
24
+ #
25
+ # You'll most likely want to reassign `environment` to your own.
26
+ #
27
+ # Rake::SprocketsTask.new do |t|
28
+ # t.environment = Foo::Assets
29
+ # end
30
+ #
31
+ def environment
32
+ if !@environment.is_a?(Sprockets::Base) && @environment.respond_to?(:call)
33
+ @environment = @environment.call
34
+ else
35
+ @environment
36
+ end
37
+ end
38
+ attr_writer :environment
39
+
40
+ # Directory to write compiled assets too. As well as the manifest file.
41
+ #
42
+ # t.output = "./public/assets"
43
+ #
44
+ attr_accessor :output
45
+
46
+ # Array of asset logical paths to compile.
47
+ #
48
+ # t.assets = %w( application.js jquery.js application.css )
49
+ #
50
+ attr_accessor :assets
51
+
52
+ # Number of old assets to keep.
53
+ attr_accessor :keep
54
+
55
+ # Logger to use during rake tasks. Defaults to using stderr.
56
+ #
57
+ # t.logger = Logger.new($stdout)
58
+ #
59
+ attr_accessor :logger
60
+
61
+ # Returns logger level Integer.
62
+ def log_level
63
+ @logger.level
64
+ end
65
+
66
+ # Set logger level with constant or symbol.
67
+ #
68
+ # t.log_level = Logger::INFO
69
+ # t.log_level = :debug
70
+ #
71
+ def log_level=(level)
72
+ if level.is_a?(Integer)
73
+ @logger.level = level
74
+ else
75
+ @logger.level = Logger.const_get(level.to_s.upcase)
76
+ end
77
+ end
78
+
79
+ def initialize(name = :assets)
80
+ @name = name
81
+ @environment = lambda { Sprockets::Environment.new(Dir.pwd) }
82
+ @logger = Logger.new($stderr)
83
+ @logger.level = Logger::INFO
84
+ @keep = 2
85
+
86
+ yield self if block_given?
87
+
88
+ define
89
+ end
90
+
91
+ # Define tasks
92
+ def define
93
+ desc name == :assets ? "Compile assets" : "Compile #{name} assets"
94
+ task name do
95
+ with_logger do
96
+ manifest.compile(assets)
97
+ end
98
+ end
99
+
100
+ desc name == :assets ? "Remove all assets" : "Remove all #{name} assets"
101
+ task "clobber_#{name}" do
102
+ with_logger do
103
+ manifest.clobber
104
+ end
105
+ end
106
+
107
+ task :clobber => ["clobber_#{name}"]
108
+
109
+ desc name == :assets ? "Clean old assets" : "Clean old #{name} assets"
110
+ task "clean_#{name}" do
111
+ with_logger do
112
+ manifest.clean(keep)
113
+ end
114
+ end
115
+
116
+ task :clean => ["clean_#{name}"]
117
+ end
118
+
119
+ private
120
+ # Returns cached indexed environment
121
+ def index
122
+ @index ||= environment.index
123
+ end
124
+
125
+ # Returns manifest for tasks
126
+ def manifest
127
+ @manifest ||= Sprockets::Manifest.new(index, output)
128
+ end
129
+
130
+ # Sub out environment logger with our rake task logger that
131
+ # writes to stderr.
132
+ def with_logger
133
+ old_logger = index.logger
134
+ index.logger = @logger
135
+ yield
136
+ ensure
137
+ index.logger = old_logger
138
+ end
139
+ end
140
+ end
data/lib/sprockets.rb CHANGED
@@ -6,6 +6,7 @@ module Sprockets
6
6
  autoload :Engines, "sprockets/engines"
7
7
  autoload :Environment, "sprockets/environment"
8
8
  autoload :Index, "sprockets/index"
9
+ autoload :Manifest, "sprockets/manifest"
9
10
 
10
11
  # Assets
11
12
  autoload :Asset, "sprockets/asset"
@@ -138,6 +138,8 @@ module Sprockets
138
138
  # Gzip contents if filename has '.gz'
139
139
  options[:compress] ||= File.extname(filename) == '.gz'
140
140
 
141
+ FileUtils.mkdir_p File.dirname(filename)
142
+
141
143
  File.open("#{filename}+", 'wb') do |f|
142
144
  if options[:compress]
143
145
  # Run contents through `Zlib`
@@ -103,6 +103,12 @@ module Sprockets
103
103
  raise NotImplementedError
104
104
  end
105
105
 
106
+ if defined? Encoding.default_external
107
+ # Define `default_external_encoding` accessor on 1.9.
108
+ # Defaults to UTF-8.
109
+ attr_accessor :default_external_encoding
110
+ end
111
+
106
112
  # Works like `Dir.entries`.
107
113
  #
108
114
  # Subclasses may cache this method.
@@ -149,7 +155,7 @@ module Sprockets
149
155
  logical_path = path
150
156
  pathname = Pathname.new(path)
151
157
 
152
- if pathname.to_s =~ /^\//
158
+ if pathname.absolute?
153
159
  return unless stat(pathname)
154
160
  logical_path = attributes_for(pathname).logical_path
155
161
  else
@@ -204,13 +210,15 @@ module Sprockets
204
210
  nil
205
211
  end
206
212
 
207
- def each_logical_path
208
- return to_enum(__method__) unless block_given?
213
+ def each_logical_path(*args)
214
+ return to_enum(__method__, *args) unless block_given?
215
+ filters = args.flatten
209
216
  files = {}
210
217
  each_file do |filename|
211
- logical_path = attributes_for(filename).logical_path
212
- yield logical_path unless files[logical_path]
213
- files[logical_path] = true
218
+ if logical_path = logical_path_for_filename(filename, filters)
219
+ yield logical_path unless files[logical_path]
220
+ files[logical_path] = true
221
+ end
214
222
  end
215
223
  nil
216
224
  end
@@ -263,5 +271,37 @@ module Sprockets
263
271
  ensure
264
272
  Thread.current[:sprockets_circular_calls] = nil if reset
265
273
  end
274
+
275
+ def logical_path_for_filename(filename, filters)
276
+ logical_path = attributes_for(filename).logical_path.to_s
277
+
278
+ if matches_filter(filters, logical_path)
279
+ return logical_path
280
+ end
281
+
282
+ # If filename is an index file, retest with alias
283
+ if File.basename(logical_path)[/[^\.]+/, 0] == 'index'
284
+ path = logical_path.sub(/\/index\./, '.')
285
+ if matches_filter(filters, path)
286
+ return path
287
+ end
288
+ end
289
+
290
+ nil
291
+ end
292
+
293
+ def matches_filter(filters, filename)
294
+ return true if filters.empty?
295
+
296
+ filters.any? do |filter|
297
+ if filter.is_a?(Regexp)
298
+ filter.match(filename)
299
+ elsif filter.respond_to?(:call)
300
+ filter.call(filename)
301
+ else
302
+ File.fnmatch(filter.to_s, filename)
303
+ end
304
+ end
305
+ end
266
306
  end
267
307
  end
@@ -6,14 +6,11 @@ module Sprockets
6
6
  module Cache
7
7
  # A simple file system cache store.
8
8
  #
9
- # environment.cache = Sprockets::Cache::FileStore.new("tmp/sprockets")
9
+ # environment.cache = Sprockets::Cache::FileStore.new("/tmp")
10
10
  #
11
11
  class FileStore
12
12
  def initialize(root)
13
13
  @root = Pathname.new(root)
14
-
15
- # Ensure directory exists
16
- FileUtils.mkdir_p @root
17
14
  end
18
15
 
19
16
  # Lookup value in cache
@@ -24,6 +21,9 @@ module Sprockets
24
21
 
25
22
  # Save value to cache
26
23
  def []=(key, value)
24
+ # Ensure directory exists
25
+ FileUtils.mkdir_p @root.join(key).dirname
26
+
27
27
  @root.join(key).open('w') { |f| Marshal.dump(value, f)}
28
28
  value
29
29
  end
@@ -11,7 +11,7 @@ module Sprockets
11
11
  # helpers by injecting them into `Environment#context_class`. Do not
12
12
  # mix them into `Context` directly.
13
13
  #
14
- # environment.instance_eval do
14
+ # environment.context_class.class_eval do
15
15
  # include MyHelper
16
16
  # def asset_url; end
17
17
  # end
@@ -22,7 +22,8 @@ module Sprockets
22
22
  # assets. See `DirectiveProcessor` for an example of this.
23
23
  class Context
24
24
  attr_reader :environment, :pathname
25
- attr_reader :_required_paths, :_dependency_paths, :_dependency_assets
25
+ attr_reader :_required_paths, :_stubbed_assets
26
+ attr_reader :_dependency_paths, :_dependency_assets
26
27
  attr_writer :__LINE__
27
28
 
28
29
  def initialize(environment, logical_path, pathname)
@@ -32,6 +33,7 @@ module Sprockets
32
33
  @__LINE__ = nil
33
34
 
34
35
  @_required_paths = []
36
+ @_stubbed_assets = Set.new
35
37
  @_dependency_paths = Set.new
36
38
  @_dependency_assets = Set.new([pathname.to_s])
37
39
  end
@@ -78,7 +80,7 @@ module Sprockets
78
80
  pathname = Pathname.new(path)
79
81
  attributes = environment.attributes_for(pathname)
80
82
 
81
- if pathname.to_s =~ /^\//
83
+ if pathname.absolute?
82
84
  pathname
83
85
 
84
86
  elsif content_type = options[:content_type]
@@ -143,6 +145,14 @@ module Sprockets
143
145
  nil
144
146
  end
145
147
 
148
+ # `stub_asset` blacklists `path` from being included in the bundle.
149
+ # `path` must be an asset which may or may not already be included
150
+ # in the bundle.
151
+ def stub_asset(path)
152
+ @_stubbed_assets << resolve(path, :content_type => :self).to_s
153
+ nil
154
+ end
155
+
146
156
  # Tests if target path is able to be safely required into the
147
157
  # current concatenation.
148
158
  def asset_requirable?(path)
@@ -168,7 +178,13 @@ module Sprockets
168
178
  if options[:data]
169
179
  result = options[:data]
170
180
  else
171
- result = Sprockets::Utils.read_unicode(pathname)
181
+ if environment.respond_to?(:default_external_encoding)
182
+ mime_type = environment.mime_types(pathname.extname)
183
+ encoding = environment.encoding_for_mime_type(mime_type)
184
+ result = Sprockets::Utils.read_unicode(pathname, encoding)
185
+ else
186
+ result = Sprockets::Utils.read_unicode(pathname)
187
+ end
172
188
  end
173
189
 
174
190
  processors.each do |processor|
@@ -260,7 +260,7 @@ module Sprockets
260
260
  root = pathname.dirname.join(path).expand_path
261
261
 
262
262
  unless (stats = stat(root)) && stats.directory?
263
- raise ArgumentError, "require_tree argument must be a directory"
263
+ raise ArgumentError, "require_directory argument must be a directory"
264
264
  end
265
265
 
266
266
  context.depend_on(root)
@@ -340,6 +340,18 @@ module Sprockets
340
340
  context.depend_on_asset(path)
341
341
  end
342
342
 
343
+ # Allows dependency to be excluded from the asset bundle.
344
+ #
345
+ # The `path` must be a valid asset and may or may not already
346
+ # be part of the bundle. Once stubbed, it is blacklisted and
347
+ # can't be brought back by any other `require`.
348
+ #
349
+ # //= stub "jquery"
350
+ #
351
+ def process_stub_directive(path)
352
+ context.stub_asset(path)
353
+ end
354
+
343
355
  # Enable Sprockets 1.x compat mode.
344
356
  #
345
357
  # Makes it possible to use the same JavaScript source
@@ -23,6 +23,10 @@ module Sprockets
23
23
  self.logger = Logger.new($stderr)
24
24
  self.logger.level = Logger::FATAL
25
25
 
26
+ if respond_to?(:default_external_encoding)
27
+ self.default_external_encoding = Encoding::UTF_8
28
+ end
29
+
26
30
  # Create a safe `Context` subclass to mutate
27
31
  @context_class = Class.new(Context)
28
32
 
@@ -14,6 +14,10 @@ module Sprockets
14
14
  def initialize(environment)
15
15
  @environment = environment
16
16
 
17
+ if environment.respond_to?(:default_external_encoding)
18
+ @default_external_encoding = environment.default_external_encoding
19
+ end
20
+
17
21
  # Copy environment attributes
18
22
  @logger = environment.logger
19
23
  @context_class = environment.context_class
@@ -0,0 +1,203 @@
1
+ require 'multi_json'
2
+ require 'time'
3
+
4
+ module Sprockets
5
+ # The Manifest logs the contents of assets compiled to a single
6
+ # directory. It records basic attributes about the asset for fast
7
+ # lookup without having to compile. A pointer from each logical path
8
+ # indicates with fingerprinted asset is the current one.
9
+ #
10
+ # The JSON is part of the public API and should be considered
11
+ # stable. This should make it easy to read from other programming
12
+ # languages and processes that don't have sprockets loaded. See
13
+ # `#assets` and `#files` for more infomation about the structure.
14
+ class Manifest
15
+ attr_reader :environment, :path, :dir
16
+
17
+ # Create new Manifest associated with an `environment`. `path` is
18
+ # a full path to the manifest json file. The file may or may not
19
+ # already exist. The dirname of the `path` will be used to write
20
+ # compiled assets to. Otherwise, if the path is a directory, the
21
+ # filename will default to "manifest.json" in that directory.
22
+ #
23
+ # Manifest.new(environment, "./public/assets/manifest.json")
24
+ #
25
+ def initialize(environment, path)
26
+ @environment = environment
27
+
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)
34
+ end
35
+
36
+ data = nil
37
+
38
+ begin
39
+ if File.exist?(@path)
40
+ data = MultiJson.decode(File.read(@path))
41
+ end
42
+ rescue MultiJson::DecodeError => e
43
+ logger.error "#{@path} is invalid: #{e.class} #{e.message}"
44
+ end
45
+
46
+ @data = data.is_a?(Hash) ? data : {}
47
+ end
48
+
49
+ # Returns internal assets mapping. Keys are logical paths which
50
+ # map to the latest fingerprinted filename.
51
+ #
52
+ # Logical path (String): Fingerprint path (String)
53
+ #
54
+ # { "application.js" => "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js",
55
+ # "jquery.js" => "jquery-ae0908555a245f8266f77df5a8edca2e.js" }
56
+ #
57
+ def assets
58
+ @data['assets'] ||= {}
59
+ end
60
+
61
+ # Returns internal file directory listing. Keys are filenames
62
+ # which map to an attributes array.
63
+ #
64
+ # Fingerprint path (String):
65
+ # logical_path: Logical path (String)
66
+ # mtime: ISO8601 mtime (String)
67
+ # digest: Base64 hex digest (String)
68
+ #
69
+ # { "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js" =>
70
+ # { 'logical_path' => "application.js",
71
+ # 'mtime' => "2011-12-13T21:47:08-06:00",
72
+ # 'digest' => "2e8e9a7c6b0aafa0c9bdeec90ea30213" } }
73
+ #
74
+ def files
75
+ @data['files'] ||= {}
76
+ end
77
+
78
+ # Compile and write asset to directory. The asset is written to a
79
+ # fingerprinted filename like
80
+ # `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
81
+ # also inserted into the manifest file.
82
+ #
83
+ # compile("application.js")
84
+ #
85
+ def compile(*args)
86
+ paths = environment.each_logical_path(*args).to_a +
87
+ args.flatten.select { |fn| Pathname.new(fn).absolute? }
88
+
89
+ paths.each do |path|
90
+ if asset = find_asset(path)
91
+ files[asset.digest_path] = {
92
+ 'logical_path' => asset.logical_path,
93
+ 'mtime' => asset.mtime.iso8601,
94
+ 'digest' => asset.digest
95
+ }
96
+ assets[asset.logical_path] = asset.digest_path
97
+
98
+ target = File.join(dir, asset.digest_path)
99
+
100
+ if File.exist?(target)
101
+ logger.debug "Skipping #{target}, already exists"
102
+ else
103
+ logger.info "Writing #{target}"
104
+ asset.write_to target
105
+ end
106
+
107
+ save
108
+ asset
109
+ end
110
+ end
111
+ end
112
+
113
+ # Removes file from directory and from manifest. `filename` must
114
+ # be the name with any directory path.
115
+ #
116
+ # manifest.remove("application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js")
117
+ #
118
+ def remove(filename)
119
+ path = File.join(dir, filename)
120
+ logical_path = files[filename]['logical_path']
121
+
122
+ if assets[logical_path] == filename
123
+ assets.delete(logical_path)
124
+ end
125
+
126
+ files.delete(filename)
127
+ FileUtils.rm(path) if File.exist?(path)
128
+
129
+ save
130
+
131
+ logger.warn "Removed #{filename}"
132
+
133
+ nil
134
+ end
135
+
136
+ # Cleanup old assets in the compile directory. By default it will
137
+ # keep the latest version plus 2 backups.
138
+ def clean(keep = 2)
139
+ self.assets.keys.each do |logical_path|
140
+ # Get assets sorted by ctime, newest first
141
+ assets = backups_for(logical_path)
142
+
143
+ # Keep the last N backups
144
+ assets = assets[keep..-1] || []
145
+
146
+ # Remove old assets
147
+ assets.each { |path, _| remove(path) }
148
+ end
149
+ end
150
+
151
+ # Wipe directive
152
+ def clobber
153
+ FileUtils.rm_r(@dir) if File.exist?(@dir)
154
+ logger.warn "Removed #{@dir}"
155
+ nil
156
+ end
157
+
158
+ protected
159
+ # Finds all the backup assets for a logical path. The latest
160
+ # version is always excluded. The return array is sorted by the
161
+ # assets mtime in descending order (Newest to oldest).
162
+ def backups_for(logical_path)
163
+ files.select { |filename, attrs|
164
+ # Matching logical paths
165
+ attrs['logical_path'] == logical_path &&
166
+ # Excluding whatever asset is the current
167
+ assets[logical_path] != filename
168
+ }.sort_by { |filename, attrs|
169
+ # Sort by timestamp
170
+ Time.parse(attrs['mtime'])
171
+ }.reverse
172
+ end
173
+
174
+ # Basic wrapper around Environment#find_asset. Logs compile time.
175
+ def find_asset(logical_path)
176
+ asset = nil
177
+ ms = benchmark do
178
+ asset = environment.find_asset(logical_path)
179
+ end
180
+ logger.warn "Compiled #{logical_path} (#{ms}ms)"
181
+ asset
182
+ end
183
+
184
+ # Persist manfiest back to FS
185
+ def save
186
+ FileUtils.mkdir_p dir
187
+ File.open(path, 'w') do |f|
188
+ f.write MultiJson.encode(@data)
189
+ end
190
+ end
191
+
192
+ private
193
+ def logger
194
+ environment.logger
195
+ end
196
+
197
+ def benchmark
198
+ start_time = Time.now.to_f
199
+ yield
200
+ ((Time.now.to_f - start_time) * 1000).to_i
201
+ end
202
+ end
203
+ end
@@ -30,6 +30,16 @@ module Sprockets
30
30
  ext = Sprockets::Utils.normalize_extension(ext)
31
31
  @mime_types[ext] = mime_type
32
32
  end
33
+
34
+ if defined? Encoding
35
+ # Returns the correct encoding for a given mime type, while falling
36
+ # back on the default external encoding, if it exists.
37
+ def encoding_for_mime_type(type)
38
+ encoding = Encoding::BINARY if type =~ %r{^(image|audio|video)/}
39
+ encoding ||= default_external_encoding if respond_to?(:default_external_encoding)
40
+ encoding
41
+ end
42
+ end
33
43
  end
34
44
 
35
45
  # Extend Sprockets module to provide global registry
@@ -94,27 +94,31 @@ module Sprockets
94
94
 
95
95
  private
96
96
  def build_required_assets(environment, context)
97
- @required_assets = []
98
- required_assets_cache = {}
97
+ @required_assets = resolve_dependencies(environment, context._required_paths + [pathname.to_s]) -
98
+ resolve_dependencies(environment, context._stubbed_assets.to_a)
99
+ end
100
+
101
+ def resolve_dependencies(environment, paths)
102
+ assets = []
103
+ cache = {}
99
104
 
100
- (context._required_paths + [pathname.to_s]).each do |path|
105
+ paths.each do |path|
101
106
  if path == self.pathname.to_s
102
- unless required_assets_cache[self]
103
- required_assets_cache[self] = true
104
- @required_assets << self
107
+ unless cache[self]
108
+ cache[self] = true
109
+ assets << self
105
110
  end
106
111
  elsif asset = environment.find_asset(path, :bundle => false)
107
112
  asset.required_assets.each do |asset_dependency|
108
- unless required_assets_cache[asset_dependency]
109
- required_assets_cache[asset_dependency] = true
110
- @required_assets << asset_dependency
113
+ unless cache[asset_dependency]
114
+ cache[asset_dependency] = true
115
+ assets << asset_dependency
111
116
  end
112
117
  end
113
118
  end
114
119
  end
115
120
 
116
- required_assets_cache.clear
117
- required_assets_cache = nil
121
+ assets
118
122
  end
119
123
 
120
124
  def build_dependency_paths(environment, context)
@@ -53,9 +53,8 @@ module Sprockets
53
53
  # Return a 404 Not Found
54
54
  not_found_response
55
55
 
56
- # Check request headers `HTTP_IF_MODIFIED_SINCE` and
57
- # `HTTP_IF_NONE_MATCH` against the assets mtime and digest
58
- elsif not_modified?(asset, env) || etag_match?(asset, env)
56
+ # Check request headers `HTTP_IF_NONE_MATCH` against the asset digest
57
+ elsif etag_match?(asset, env)
59
58
  logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)"
60
59
 
61
60
  # Return a 304 Not Modified
@@ -174,12 +173,6 @@ module Sprockets
174
173
  gsub('/', '\\\\002f ')
175
174
  end
176
175
 
177
- # Compare the requests `HTTP_IF_MODIFIED_SINCE` against the
178
- # assets mtime
179
- def not_modified?(asset, env)
180
- env["HTTP_IF_MODIFIED_SINCE"] == asset.mtime.httpdate
181
- end
182
-
183
176
  # Compare the requests `HTTP_IF_NONE_MATCH` against the assets digest
184
177
  def etag_match?(asset, env)
185
178
  env["HTTP_IF_NONE_MATCH"] == etag(asset)
@@ -23,6 +23,8 @@ module Sprockets
23
23
  # Gzip contents if filename has '.gz'
24
24
  options[:compress] ||= File.extname(filename) == '.gz'
25
25
 
26
+ FileUtils.mkdir_p File.dirname(filename)
27
+
26
28
  if options[:compress]
27
29
  # Open file and run it through `Zlib`
28
30
  pathname.open('rb') do |rd|
@@ -8,19 +8,21 @@ module Sprockets
8
8
  # encoding and we want to avoid syntax errors in other interpreters.
9
9
  UTF8_BOM_PATTERN = Regexp.new("\\A\uFEFF".encode('utf-8'))
10
10
 
11
- def self.read_unicode(pathname)
12
- pathname.read.tap do |data|
13
- # Eager validate the file's encoding. In most cases we
14
- # expect it to be UTF-8 unless `default_external` is set to
15
- # something else. An error is usually raised if the file is
16
- # saved as UTF-16 when we expected UTF-8.
17
- if !data.valid_encoding?
18
- raise EncodingError, "#{pathname} has a invalid " +
19
- "#{data.encoding} byte sequence"
11
+ def self.read_unicode(pathname, external_encoding = Encoding.default_external)
12
+ pathname.open("r:#{external_encoding}") do |f|
13
+ f.read.tap do |data|
14
+ # Eager validate the file's encoding. In most cases we
15
+ # expect it to be UTF-8 unless `default_external` is set to
16
+ # something else. An error is usually raised if the file is
17
+ # saved as UTF-16 when we expected UTF-8.
18
+ if !data.valid_encoding?
19
+ raise EncodingError, "#{pathname} has a invalid " +
20
+ "#{data.encoding} byte sequence"
20
21
 
21
- # If the file is UTF-8 and theres a BOM, strip it for safe concatenation.
22
- elsif data.encoding.name == "UTF-8" && data =~ UTF8_BOM_PATTERN
23
- data.sub!(UTF8_BOM_PATTERN, "")
22
+ # If the file is UTF-8 and theres a BOM, strip it for safe concatenation.
23
+ elsif data.encoding.name == "UTF-8" && data =~ UTF8_BOM_PATTERN
24
+ data.sub!(UTF8_BOM_PATTERN, "")
25
+ end
24
26
  end
25
27
  end
26
28
  end
@@ -1,3 +1,3 @@
1
1
  module Sprockets
2
- VERSION = "2.1.0"
2
+ VERSION = "2.2.0"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sprockets
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 2.1.0
10
+ version: 2.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sam Stephenson
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-11-11 00:00:00 -06:00
19
+ date: 2012-01-10 00:00:00 -06:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -35,7 +35,7 @@ dependencies:
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
37
  - !ruby/object:Gem::Dependency
38
- name: rack
38
+ name: multi_json
39
39
  prerelease: false
40
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
41
  none: false
@@ -50,9 +50,24 @@ dependencies:
50
50
  type: :runtime
51
51
  version_requirements: *id002
52
52
  - !ruby/object:Gem::Dependency
53
- name: tilt
53
+ name: rack
54
54
  prerelease: false
55
55
  requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 15
61
+ segments:
62
+ - 1
63
+ - 0
64
+ version: "1.0"
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: tilt
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
56
71
  none: false
57
72
  requirements:
58
73
  - - ~>
@@ -71,11 +86,11 @@ dependencies:
71
86
  - 0
72
87
  version: 1.3.0
73
88
  type: :runtime
74
- version_requirements: *id003
89
+ version_requirements: *id004
75
90
  - !ruby/object:Gem::Dependency
76
91
  name: coffee-script
77
92
  prerelease: false
78
- requirement: &id004 !ruby/object:Gem::Requirement
93
+ requirement: &id005 !ruby/object:Gem::Requirement
79
94
  none: false
80
95
  requirements:
81
96
  - - ~>
@@ -86,11 +101,27 @@ dependencies:
86
101
  - 0
87
102
  version: "2.0"
88
103
  type: :development
89
- version_requirements: *id004
104
+ version_requirements: *id005
105
+ - !ruby/object:Gem::Dependency
106
+ name: coffee-script-source
107
+ prerelease: false
108
+ requirement: &id006 !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ~>
112
+ - !ruby/object:Gem::Version
113
+ hash: 31
114
+ segments:
115
+ - 1
116
+ - 2
117
+ - 0
118
+ version: 1.2.0
119
+ type: :development
120
+ version_requirements: *id006
90
121
  - !ruby/object:Gem::Dependency
91
122
  name: eco
92
123
  prerelease: false
93
- requirement: &id005 !ruby/object:Gem::Requirement
124
+ requirement: &id007 !ruby/object:Gem::Requirement
94
125
  none: false
95
126
  requirements:
96
127
  - - ~>
@@ -101,11 +132,11 @@ dependencies:
101
132
  - 0
102
133
  version: "1.0"
103
134
  type: :development
104
- version_requirements: *id005
135
+ version_requirements: *id007
105
136
  - !ruby/object:Gem::Dependency
106
137
  name: ejs
107
138
  prerelease: false
108
- requirement: &id006 !ruby/object:Gem::Requirement
139
+ requirement: &id008 !ruby/object:Gem::Requirement
109
140
  none: false
110
141
  requirements:
111
142
  - - ~>
@@ -116,11 +147,11 @@ dependencies:
116
147
  - 0
117
148
  version: "1.0"
118
149
  type: :development
119
- version_requirements: *id006
150
+ version_requirements: *id008
120
151
  - !ruby/object:Gem::Dependency
121
152
  name: execjs
122
153
  prerelease: false
123
- requirement: &id007 !ruby/object:Gem::Requirement
154
+ requirement: &id009 !ruby/object:Gem::Requirement
124
155
  none: false
125
156
  requirements:
126
157
  - - ~>
@@ -131,11 +162,11 @@ dependencies:
131
162
  - 0
132
163
  version: "1.0"
133
164
  type: :development
134
- version_requirements: *id007
165
+ version_requirements: *id009
135
166
  - !ruby/object:Gem::Dependency
136
167
  name: json
137
168
  prerelease: false
138
- requirement: &id008 !ruby/object:Gem::Requirement
169
+ requirement: &id010 !ruby/object:Gem::Requirement
139
170
  none: false
140
171
  requirements:
141
172
  - - ">="
@@ -145,11 +176,11 @@ dependencies:
145
176
  - 0
146
177
  version: "0"
147
178
  type: :development
148
- version_requirements: *id008
179
+ version_requirements: *id010
149
180
  - !ruby/object:Gem::Dependency
150
181
  name: rack-test
151
182
  prerelease: false
152
- requirement: &id009 !ruby/object:Gem::Requirement
183
+ requirement: &id011 !ruby/object:Gem::Requirement
153
184
  none: false
154
185
  requirements:
155
186
  - - ">="
@@ -159,11 +190,11 @@ dependencies:
159
190
  - 0
160
191
  version: "0"
161
192
  type: :development
162
- version_requirements: *id009
193
+ version_requirements: *id011
163
194
  - !ruby/object:Gem::Dependency
164
195
  name: rake
165
196
  prerelease: false
166
- requirement: &id010 !ruby/object:Gem::Requirement
197
+ requirement: &id012 !ruby/object:Gem::Requirement
167
198
  none: false
168
199
  requirements:
169
200
  - - ">="
@@ -173,13 +204,13 @@ dependencies:
173
204
  - 0
174
205
  version: "0"
175
206
  type: :development
176
- version_requirements: *id010
207
+ version_requirements: *id012
177
208
  description: Sprockets is a Rack-based asset packaging system that concatenates and serves JavaScript, CoffeeScript, CSS, LESS, Sass, and SCSS.
178
209
  email:
179
210
  - sstephenson@gmail.com
180
211
  - josh@joshpeek.com
181
- executables: []
182
-
212
+ executables:
213
+ - sprockets
183
214
  extensions: []
184
215
 
185
216
  extra_rdoc_files: []
@@ -187,6 +218,7 @@ extra_rdoc_files: []
187
218
  files:
188
219
  - README.md
189
220
  - LICENSE
221
+ - lib/rake/sprocketstask.rb
190
222
  - lib/sprockets/asset.rb
191
223
  - lib/sprockets/asset_attributes.rb
192
224
  - lib/sprockets/base.rb
@@ -203,6 +235,7 @@ files:
203
235
  - lib/sprockets/errors.rb
204
236
  - lib/sprockets/index.rb
205
237
  - lib/sprockets/jst_processor.rb
238
+ - lib/sprockets/manifest.rb
206
239
  - lib/sprockets/mime.rb
207
240
  - lib/sprockets/processed_asset.rb
208
241
  - lib/sprockets/processing.rb
@@ -214,6 +247,7 @@ files:
214
247
  - lib/sprockets/utils.rb
215
248
  - lib/sprockets/version.rb
216
249
  - lib/sprockets.rb
250
+ - bin/sprockets
217
251
  has_rdoc: true
218
252
  homepage: http://getsprockets.org/
219
253
  licenses: []