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 +8 -0
- data/bin/sprockets +75 -0
- data/lib/rake/sprocketstask.rb +136 -0
- data/lib/sprockets.rb +1 -0
- data/lib/sprockets/asset.rb +2 -0
- data/lib/sprockets/base.rb +45 -5
- data/lib/sprockets/context.rb +17 -3
- data/lib/sprockets/directive_processor.rb +12 -0
- data/lib/sprockets/environment.rb +4 -0
- data/lib/sprockets/index.rb +4 -0
- data/lib/sprockets/manifest.rb +203 -0
- data/lib/sprockets/processed_asset.rb +15 -11
- data/lib/sprockets/server.rb +8 -8
- data/lib/sprockets/static_asset.rb +2 -0
- data/lib/sprockets/utils.rb +14 -12
- data/lib/sprockets/version.rb +1 -1
- metadata +198 -146
- checksums.yaml +0 -7
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.
|
data/bin/sprockets
ADDED
@@ -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
|
data/lib/sprockets.rb
CHANGED
data/lib/sprockets/asset.rb
CHANGED
@@ -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`
|
data/lib/sprockets/base.rb
CHANGED
@@ -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 =
|
212
|
-
|
213
|
-
|
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
|
data/lib/sprockets/context.rb
CHANGED
@@ -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.
|
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, :
|
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
|
-
|
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
|
|
data/lib/sprockets/index.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
105
|
+
paths.each do |path|
|
101
106
|
if path == self.pathname.to_s
|
102
|
-
unless
|
103
|
-
|
104
|
-
|
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
|
109
|
-
|
110
|
-
|
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
|
-
|
117
|
-
required_assets_cache = nil
|
121
|
+
assets
|
118
122
|
end
|
119
123
|
|
120
124
|
def build_dependency_paths(environment, context)
|
data/lib/sprockets/server.rb
CHANGED
@@ -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?(
|
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
|
-
|
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})\.[^.]
|
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|
|
data/lib/sprockets/utils.rb
CHANGED
@@ -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.
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
"#{
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
data/lib/sprockets/version.rb
CHANGED
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
128
|
-
|
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
|
-
|
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
|
-
|
142
|
-
|
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
|
-
|
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
|
-
|
156
|
-
|
182
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
183
|
+
none: false
|
184
|
+
requirements:
|
157
185
|
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
|
169
|
-
|
202
|
+
|
203
|
+
files:
|
170
204
|
- README.md
|
171
|
-
-
|
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
|
-
|
239
|
+
|
201
240
|
post_install_message:
|
202
241
|
rdoc_options: []
|
203
|
-
|
242
|
+
|
243
|
+
require_paths:
|
204
244
|
- lib
|
205
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
206
|
-
|
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
|
-
|
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:
|
268
|
+
rubygems_version: 1.6.2
|
218
269
|
signing_key:
|
219
|
-
specification_version:
|
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
|