sprockets 2.1.4 → 2.2.0.beta

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,6 +354,14 @@ 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
+
357
365
  **2.1.2** (November 20, 2011)
358
366
 
359
367
  * Disabled If-Modified-Since server checks. Fixes some browser caching issues when serving the asset body only. If-None-Match caching is sufficent.
@@ -0,0 +1,75 @@
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
+ environment.append_path(".")
68
+
69
+ if manifest
70
+ manifest.compile(filenames)
71
+ else
72
+ filenames.each do |filename|
73
+ puts environment.find_asset(filename).to_s
74
+ end
75
+ end
@@ -0,0 +1,136 @@
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
+ # Logger to use during rake tasks. Defaults to using stderr.
53
+ #
54
+ # t.logger = Logger.new($stdout)
55
+ #
56
+ attr_accessor :logger
57
+
58
+ # Returns logger level Integer.
59
+ def log_level
60
+ @logger.level
61
+ end
62
+
63
+ # Set logger level with constant or symbol.
64
+ #
65
+ # t.log_level = Logger::INFO
66
+ # t.log_level = :debug
67
+ #
68
+ def log_level=(level)
69
+ if level.is_a?(Integer)
70
+ @logger.level = level
71
+ else
72
+ @logger.level = Logger.const_get(level.to_s.upcase)
73
+ end
74
+ end
75
+
76
+ def initialize(name = :assets)
77
+ @name = name
78
+ @environment = lambda { Sprockets::Environment.new(Dir.pwd) }
79
+ @logger = Logger.new($stderr)
80
+ @logger.level = Logger::WARN
81
+
82
+ yield self if block_given?
83
+
84
+ define
85
+ end
86
+
87
+ # Define tasks
88
+ def define
89
+ desc name == :assets ? "Compile assets" : "Compile #{name} assets"
90
+ task name do
91
+ with_logger do
92
+ manifest.compile(assets)
93
+ end
94
+ end
95
+
96
+ desc name == :assets ? "Remove all assets" : "Remove all #{name} assets"
97
+ task "clobber_#{name}" do
98
+ with_logger do
99
+ manifest.clobber
100
+ end
101
+ end
102
+
103
+ task :clobber => ["clobber_#{name}"]
104
+
105
+ desc name == :assets ? "Clean old assets" : "Clean old #{name} assets"
106
+ task "clean_#{name}" do
107
+ with_logger do
108
+ manifest.clean
109
+ end
110
+ end
111
+
112
+ task :clean => ["clean_#{name}"]
113
+ end
114
+
115
+ private
116
+ # Returns cached indexed environment
117
+ def index
118
+ @index ||= environment.index
119
+ end
120
+
121
+ # Returns manifest for tasks
122
+ def manifest
123
+ @manifest ||= Sprockets::Manifest.new(index, output)
124
+ end
125
+
126
+ # Sub out environment logger with our rake task logger that
127
+ # writes to stderr.
128
+ def with_logger
129
+ old_logger = index.logger
130
+ index.logger = @logger
131
+ yield
132
+ ensure
133
+ index.logger = old_logger
134
+ end
135
+ end
136
+ end
@@ -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.
@@ -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
@@ -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
@@ -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,11 @@ 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
+ result = Sprockets::Utils.read_unicode(pathname, environment.default_external_encoding)
183
+ else
184
+ result = Sprockets::Utils.read_unicode(pathname)
185
+ end
172
186
  end
173
187
 
174
188
  processors.each do |processor|
@@ -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
@@ -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)
@@ -25,6 +25,11 @@ module Sprockets
25
25
 
26
26
  msg = "Served asset #{env['PATH_INFO']} -"
27
27
 
28
+ # URLs containing a `".."` are rejected for security reasons.
29
+ if forbidden_request?(env)
30
+ return forbidden_response
31
+ end
32
+
28
33
  # Mark session as "skipped" so no `Set-Cookie` header is set
29
34
  env['rack.session.options'] ||= {}
30
35
  env['rack.session.options'][:defer] = true
@@ -38,11 +43,6 @@ module Sprockets
38
43
  path = path.sub("-#{fingerprint}", '')
39
44
  end
40
45
 
41
- # URLs containing a `".."` are rejected for security reasons.
42
- if forbidden_request?(path)
43
- return forbidden_response
44
- end
45
-
46
46
  # Look up the asset.
47
47
  asset = find_asset(path, :bundle => !body_only?(env))
48
48
 
@@ -85,12 +85,12 @@ module Sprockets
85
85
  end
86
86
 
87
87
  private
88
- def forbidden_request?(path)
88
+ def forbidden_request?(env)
89
89
  # Prevent access to files elsewhere on the file system
90
90
  #
91
91
  # http://example.org/assets/../../../etc/passwd
92
92
  #
93
- path.include?("..") || Pathname.new(path).absolute?
93
+ env["PATH_INFO"].include?("..")
94
94
  end
95
95
 
96
96
  # Returns a 403 Forbidden response tuple
@@ -222,7 +222,7 @@ module Sprockets
222
222
  # # => "0aa2105d29558f3eb790d411d7d8fb66"
223
223
  #
224
224
  def path_fingerprint(path)
225
- path[/-([0-9a-f]{7,40})\.[^.]+\z/, 1]
225
+ path[/-([0-9a-f]{7,40})\.[^.]+$/, 1]
226
226
  end
227
227
 
228
228
  # URI.unescape is deprecated on 1.9. We need to use URI::Parser
@@ -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.4"
2
+ VERSION = "2.2.0.beta"
3
3
  end
metadata CHANGED
@@ -1,174 +1,209 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: sprockets
3
- version: !ruby/object:Gem::Version
4
- version: 2.1.4
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31098225
5
+ prerelease: 6
6
+ segments:
7
+ - 2
8
+ - 2
9
+ - 0
10
+ - beta
11
+ version: 2.2.0.beta
5
12
  platform: ruby
6
- authors:
13
+ authors:
7
14
  - Sam Stephenson
8
15
  - Joshua Peek
9
16
  autorequire:
10
17
  bindir: bin
11
18
  cert_chain: []
12
- date: 2014-10-28 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
19
+
20
+ date: 2011-12-16 00:00:00 -06:00
21
+ default_executable:
22
+ dependencies:
23
+ - !ruby/object:Gem::Dependency
15
24
  name: hike
16
- requirement: !ruby/object:Gem::Requirement
17
- requirements:
18
- - - "~>"
19
- - !ruby/object:Gem::Version
20
- version: '1.2'
25
+ prerelease: false
26
+ requirement: &id001 !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ~>
30
+ - !ruby/object:Gem::Version
31
+ hash: 11
32
+ segments:
33
+ - 1
34
+ - 2
35
+ version: "1.2"
21
36
  type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: multi_json
22
40
  prerelease: false
23
- version_requirements: !ruby/object:Gem::Requirement
24
- requirements:
25
- - - "~>"
26
- - !ruby/object:Gem::Version
27
- version: '1.2'
28
- - !ruby/object:Gem::Dependency
29
- name: rack
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: '1.0'
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: 15
47
+ segments:
48
+ - 1
49
+ - 0
50
+ version: "1.0"
35
51
  type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: rack
36
55
  prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '1.0'
42
- - !ruby/object:Gem::Dependency
43
- name: tilt
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: '1.1'
49
- - - "!="
50
- - !ruby/object:Gem::Version
51
- version: 1.3.0
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 15
62
+ segments:
63
+ - 1
64
+ - 0
65
+ version: "1.0"
52
66
  type: :runtime
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: tilt
53
70
  prerelease: false
54
- version_requirements: !ruby/object:Gem::Requirement
55
- requirements:
56
- - - "~>"
57
- - !ruby/object:Gem::Version
58
- version: '1.1'
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ hash: 13
77
+ segments:
78
+ - 1
79
+ - 1
80
+ version: "1.1"
59
81
  - - "!="
60
- - !ruby/object:Gem::Version
82
+ - !ruby/object:Gem::Version
83
+ hash: 27
84
+ segments:
85
+ - 1
86
+ - 3
87
+ - 0
61
88
  version: 1.3.0
62
- - !ruby/object:Gem::Dependency
89
+ type: :runtime
90
+ version_requirements: *id004
91
+ - !ruby/object:Gem::Dependency
63
92
  name: coffee-script
64
- requirement: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '2.0'
69
- type: :development
70
93
  prerelease: false
71
- version_requirements: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '2.0'
76
- - !ruby/object:Gem::Dependency
77
- name: eco
78
- requirement: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '1.0'
94
+ requirement: &id005 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ~>
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 2
102
+ - 0
103
+ version: "2.0"
83
104
  type: :development
105
+ version_requirements: *id005
106
+ - !ruby/object:Gem::Dependency
107
+ name: eco
84
108
  prerelease: false
85
- version_requirements: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '1.0'
90
- - !ruby/object:Gem::Dependency
91
- name: ejs
92
- requirement: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '1.0'
109
+ requirement: &id006 !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ~>
113
+ - !ruby/object:Gem::Version
114
+ hash: 15
115
+ segments:
116
+ - 1
117
+ - 0
118
+ version: "1.0"
97
119
  type: :development
120
+ version_requirements: *id006
121
+ - !ruby/object:Gem::Dependency
122
+ name: ejs
98
123
  prerelease: false
99
- version_requirements: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '1.0'
104
- - !ruby/object:Gem::Dependency
105
- name: execjs
106
- requirement: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '1.0'
124
+ requirement: &id007 !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ~>
128
+ - !ruby/object:Gem::Version
129
+ hash: 15
130
+ segments:
131
+ - 1
132
+ - 0
133
+ version: "1.0"
111
134
  type: :development
135
+ version_requirements: *id007
136
+ - !ruby/object:Gem::Dependency
137
+ name: execjs
112
138
  prerelease: false
113
- version_requirements: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '1.0'
118
- - !ruby/object:Gem::Dependency
119
- name: json
120
- requirement: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
139
+ requirement: &id008 !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ~>
143
+ - !ruby/object:Gem::Version
144
+ hash: 15
145
+ segments:
146
+ - 1
147
+ - 0
148
+ version: "1.0"
125
149
  type: :development
150
+ version_requirements: *id008
151
+ - !ruby/object:Gem::Dependency
152
+ name: json
126
153
  prerelease: false
127
- version_requirements: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- - !ruby/object:Gem::Dependency
133
- name: rack-test
134
- requirement: !ruby/object:Gem::Requirement
135
- requirements:
154
+ requirement: &id009 !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
136
157
  - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
158
+ - !ruby/object:Gem::Version
159
+ hash: 3
160
+ segments:
161
+ - 0
162
+ version: "0"
139
163
  type: :development
164
+ version_requirements: *id009
165
+ - !ruby/object:Gem::Dependency
166
+ name: rack-test
140
167
  prerelease: false
141
- version_requirements: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- - !ruby/object:Gem::Dependency
147
- name: rake
148
- requirement: !ruby/object:Gem::Requirement
149
- requirements:
168
+ requirement: &id010 !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
150
171
  - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
172
+ - !ruby/object:Gem::Version
173
+ hash: 3
174
+ segments:
175
+ - 0
176
+ version: "0"
153
177
  type: :development
178
+ version_requirements: *id010
179
+ - !ruby/object:Gem::Dependency
180
+ name: rake
154
181
  prerelease: false
155
- version_requirements: !ruby/object:Gem::Requirement
156
- requirements:
182
+ requirement: &id011 !ruby/object:Gem::Requirement
183
+ none: false
184
+ requirements:
157
185
  - - ">="
158
- - !ruby/object:Gem::Version
159
- version: '0'
160
- description: Sprockets is a Rack-based asset packaging system that concatenates and
161
- serves JavaScript, CoffeeScript, CSS, LESS, Sass, and SCSS.
162
- email:
186
+ - !ruby/object:Gem::Version
187
+ hash: 3
188
+ segments:
189
+ - 0
190
+ version: "0"
191
+ type: :development
192
+ version_requirements: *id011
193
+ description: Sprockets is a Rack-based asset packaging system that concatenates and serves JavaScript, CoffeeScript, CSS, LESS, Sass, and SCSS.
194
+ email:
163
195
  - sstephenson@gmail.com
164
196
  - josh@joshpeek.com
165
- executables: []
197
+ executables:
198
+ - sprockets
166
199
  extensions: []
200
+
167
201
  extra_rdoc_files: []
168
- files:
169
- - LICENSE
202
+
203
+ files:
170
204
  - README.md
171
- - lib/sprockets.rb
205
+ - LICENSE
206
+ - lib/rake/sprocketstask.rb
172
207
  - lib/sprockets/asset.rb
173
208
  - lib/sprockets/asset_attributes.rb
174
209
  - lib/sprockets/base.rb
@@ -185,6 +220,7 @@ files:
185
220
  - lib/sprockets/errors.rb
186
221
  - lib/sprockets/index.rb
187
222
  - lib/sprockets/jst_processor.rb
223
+ - lib/sprockets/manifest.rb
188
224
  - lib/sprockets/mime.rb
189
225
  - lib/sprockets/processed_asset.rb
190
226
  - lib/sprockets/processing.rb
@@ -195,27 +231,43 @@ files:
195
231
  - lib/sprockets/trail.rb
196
232
  - lib/sprockets/utils.rb
197
233
  - lib/sprockets/version.rb
234
+ - lib/sprockets.rb
235
+ - bin/sprockets
236
+ has_rdoc: true
198
237
  homepage: http://getsprockets.org/
199
238
  licenses: []
200
- metadata: {}
239
+
201
240
  post_install_message:
202
241
  rdoc_options: []
203
- require_paths:
242
+
243
+ require_paths:
204
244
  - lib
205
- required_ruby_version: !ruby/object:Gem::Requirement
206
- requirements:
207
- - - ">="
208
- - !ruby/object:Gem::Version
209
- version: '0'
210
- required_rubygems_version: !ruby/object:Gem::Requirement
211
- requirements:
245
+ required_ruby_version: !ruby/object:Gem::Requirement
246
+ none: false
247
+ requirements:
212
248
  - - ">="
213
- - !ruby/object:Gem::Version
214
- version: '0'
249
+ - !ruby/object:Gem::Version
250
+ hash: 3
251
+ segments:
252
+ - 0
253
+ version: "0"
254
+ required_rubygems_version: !ruby/object:Gem::Requirement
255
+ none: false
256
+ requirements:
257
+ - - ">"
258
+ - !ruby/object:Gem::Version
259
+ hash: 25
260
+ segments:
261
+ - 1
262
+ - 3
263
+ - 1
264
+ version: 1.3.1
215
265
  requirements: []
266
+
216
267
  rubyforge_project: sprockets
217
- rubygems_version: 2.2.2
268
+ rubygems_version: 1.6.2
218
269
  signing_key:
219
- specification_version: 4
270
+ specification_version: 3
220
271
  summary: Rack-based asset packaging system
221
272
  test_files: []
273
+
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: b51e06b67ce29e2f81d65f3ab796288e530749f2
4
- data.tar.gz: bf24d81285d0f2e2c69b6669bba9213f7c038e57
5
- SHA512:
6
- metadata.gz: fabae91920a5939f2bc995f144917bf962646f9ae11fc030a1e953fbe5ca44fe7bf9f1ee6994424a726400f9cbae2d8e279b961f2c9a4867bed92bca70461c55
7
- data.tar.gz: f1926792b20cccc35f1af2c635bea28d55d5f9893b3efa5da36e854184f82f84292d03ad26a82fc70b0219629012993b1b260c2bbac233dab7490eea17bc1174