sprockets 1.0.2 → 2.0.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/LICENSE +21 -0
- data/README.md +356 -0
- data/lib/sprockets.rb +26 -37
- data/lib/sprockets/asset.rb +212 -0
- data/lib/sprockets/asset_attributes.rb +158 -0
- data/lib/sprockets/base.rb +163 -0
- data/lib/sprockets/bundled_asset.rb +258 -0
- data/lib/sprockets/cache/file_store.rb +41 -0
- data/lib/sprockets/caching.rb +123 -0
- data/lib/sprockets/charset_normalizer.rb +41 -0
- data/lib/sprockets/context.rb +217 -0
- data/lib/sprockets/digest.rb +67 -0
- data/lib/sprockets/directive_processor.rb +380 -0
- data/lib/sprockets/eco_template.rb +38 -0
- data/lib/sprockets/ejs_template.rb +37 -0
- data/lib/sprockets/engines.rb +98 -0
- data/lib/sprockets/environment.rb +81 -40
- data/lib/sprockets/errors.rb +17 -0
- data/lib/sprockets/index.rb +79 -0
- data/lib/sprockets/jst_processor.rb +26 -0
- data/lib/sprockets/mime.rb +38 -0
- data/lib/sprockets/processing.rb +280 -0
- data/lib/sprockets/processor.rb +32 -0
- data/lib/sprockets/safety_colons.rb +28 -0
- data/lib/sprockets/server.rb +272 -0
- data/lib/sprockets/static_asset.rb +86 -0
- data/lib/sprockets/trail.rb +114 -0
- data/lib/sprockets/utils.rb +67 -0
- data/lib/sprockets/version.rb +1 -7
- metadata +212 -64
- data/Rakefile +0 -19
- data/bin/sprocketize +0 -54
- data/ext/nph-sprockets.cgi +0 -127
- data/lib/sprockets/concatenation.rb +0 -36
- data/lib/sprockets/error.rb +0 -5
- data/lib/sprockets/pathname.rb +0 -37
- data/lib/sprockets/preprocessor.rb +0 -91
- data/lib/sprockets/secretary.rb +0 -106
- data/lib/sprockets/source_file.rb +0 -54
- data/lib/sprockets/source_line.rb +0 -82
- data/test/fixtures/assets/images/script_with_assets/one.png +0 -1
- data/test/fixtures/assets/images/script_with_assets/two.png +0 -1
- data/test/fixtures/assets/stylesheets/script_with_assets.css +0 -1
- data/test/fixtures/constants.yml +0 -1
- data/test/fixtures/double_slash_comments_that_are_not_requires_should_be_ignored_when_strip_comments_is_false.js +0 -8
- data/test/fixtures/double_slash_comments_that_are_not_requires_should_be_removed_by_default.js +0 -2
- data/test/fixtures/multiline_comments_should_be_removed_by_default.js +0 -4
- data/test/fixtures/requiring_a_file_after_it_has_already_been_required_should_do_nothing.js +0 -5
- data/test/fixtures/requiring_a_file_that_does_not_exist_should_raise_an_error.js +0 -1
- data/test/fixtures/requiring_a_single_file_should_replace_the_require_comment_with_the_file_contents.js +0 -3
- data/test/fixtures/requiring_the_current_file_should_do_nothing.js +0 -1
- data/test/fixtures/src/constants.yml +0 -3
- data/test/fixtures/src/foo.js +0 -1
- data/test/fixtures/src/foo/bar.js +0 -4
- data/test/fixtures/src/foo/foo.js +0 -1
- data/test/fixtures/src/script_with_assets.js +0 -3
- data/test/test_concatenation.rb +0 -28
- data/test/test_environment.rb +0 -64
- data/test/test_helper.rb +0 -55
- data/test/test_pathname.rb +0 -43
- data/test/test_preprocessor.rb +0 -107
- data/test/test_secretary.rb +0 -83
- data/test/test_source_file.rb +0 -34
- data/test/test_source_line.rb +0 -89
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# `Processor` creates an anonymous processor class from a block.
|
5
|
+
#
|
6
|
+
# register_preprocessor :my_processor do |context, data|
|
7
|
+
# # ...
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
class Processor < Tilt::Template
|
11
|
+
# `processor` is a lambda or block
|
12
|
+
def self.processor
|
13
|
+
@processor
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.name
|
17
|
+
"Sprockets::Processor (#{@name})"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.to_s
|
21
|
+
name
|
22
|
+
end
|
23
|
+
|
24
|
+
def prepare
|
25
|
+
end
|
26
|
+
|
27
|
+
# Call processor block with `context` and `data`.
|
28
|
+
def evaluate(context, locals)
|
29
|
+
self.class.processor.call(context, data)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# For JS developers who are colonfobic, concatenating JS files using
|
5
|
+
# the module pattern usually leads to syntax errors.
|
6
|
+
#
|
7
|
+
# The `SafetyColons` processor will insert missing semicolons to the
|
8
|
+
# end of the file.
|
9
|
+
#
|
10
|
+
# This behavior can be disabled with:
|
11
|
+
#
|
12
|
+
# environment.unregister_postprocessor 'application/javascript', Sprockets::SafetyColons
|
13
|
+
#
|
14
|
+
class SafetyColons < Tilt::Template
|
15
|
+
def prepare
|
16
|
+
end
|
17
|
+
|
18
|
+
def evaluate(context, locals, &block)
|
19
|
+
# If the file is blank or ends in a semicolon, leave it as is
|
20
|
+
if data =~ /\A\s*\Z/m || data =~ /;\s*\Z/m
|
21
|
+
data
|
22
|
+
else
|
23
|
+
# Otherwise, append a semicolon and newline
|
24
|
+
"#{data};\n"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
# `Server` is a concern mixed into `Environment` and
|
6
|
+
# `Index` that provides a Rack compatible `call`
|
7
|
+
# interface and url generation helpers.
|
8
|
+
module Server
|
9
|
+
# `call` implements the Rack 1.x specification which accepts an
|
10
|
+
# `env` Hash and returns a three item tuple with the status code,
|
11
|
+
# headers, and body.
|
12
|
+
#
|
13
|
+
# Mapping your environment at a url prefix will serve all assets
|
14
|
+
# in the path.
|
15
|
+
#
|
16
|
+
# map "/assets" do
|
17
|
+
# run Sprockets::Environment.new
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# A request for `"/assets/foo/bar.js"` will search your
|
21
|
+
# environment for `"foo/bar.js"`.
|
22
|
+
def call(env)
|
23
|
+
start_time = Time.now.to_f
|
24
|
+
time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
|
25
|
+
|
26
|
+
msg = "Served asset #{env['PATH_INFO']} -"
|
27
|
+
|
28
|
+
# URLs containing a `".."` are rejected for security reasons.
|
29
|
+
if forbidden_request?(env)
|
30
|
+
return forbidden_response
|
31
|
+
end
|
32
|
+
|
33
|
+
# Mark session as "skipped" so no `Set-Cookie` header is set
|
34
|
+
env['rack.session.options'] ||= {}
|
35
|
+
env['rack.session.options'][:defer] = true
|
36
|
+
env['rack.session.options'][:skip] = true
|
37
|
+
|
38
|
+
# Extract the path from everything after the leading slash
|
39
|
+
path = env['PATH_INFO'].to_s.sub(/^\//, '')
|
40
|
+
|
41
|
+
# Look up the asset.
|
42
|
+
asset = find_asset(path)
|
43
|
+
asset.to_a if asset
|
44
|
+
|
45
|
+
# `find_asset` returns nil if the asset doesn't exist
|
46
|
+
if asset.nil?
|
47
|
+
logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
|
48
|
+
|
49
|
+
# Return a 404 Not Found
|
50
|
+
not_found_response
|
51
|
+
|
52
|
+
# Check request headers `HTTP_IF_MODIFIED_SINCE` and
|
53
|
+
# `HTTP_IF_NONE_MATCH` against the assets mtime and digest
|
54
|
+
elsif not_modified?(asset, env) || etag_match?(asset, env)
|
55
|
+
logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)"
|
56
|
+
|
57
|
+
# Return a 304 Not Modified
|
58
|
+
not_modified_response(asset, env)
|
59
|
+
|
60
|
+
else
|
61
|
+
logger.info "#{msg} 200 OK (#{time_elapsed.call}ms)"
|
62
|
+
|
63
|
+
# Return a 200 with the asset contents
|
64
|
+
ok_response(asset, env)
|
65
|
+
end
|
66
|
+
rescue Exception => e
|
67
|
+
logger.error "Error compiling asset #{path}:"
|
68
|
+
logger.error "#{e.class.name}: #{e.message}"
|
69
|
+
|
70
|
+
case content_type_of(path)
|
71
|
+
when "application/javascript"
|
72
|
+
# Re-throw JavaScript asset exceptions to the browser
|
73
|
+
logger.info "#{msg} 500 Internal Server Error\n\n"
|
74
|
+
return javascript_exception_response(e)
|
75
|
+
when "text/css"
|
76
|
+
# Display CSS asset exceptions in the browser
|
77
|
+
logger.info "#{msg} 500 Internal Server Error\n\n"
|
78
|
+
return css_exception_response(e)
|
79
|
+
else
|
80
|
+
raise
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Deprecated: `path` is a url helper that looks up an asset given a
|
85
|
+
# `logical_path` and returns a path String. By default, the
|
86
|
+
# asset's digest fingerprint is spliced into the filename.
|
87
|
+
#
|
88
|
+
# /assets/application-3676d55f84497cbeadfc614c1b1b62fc.js
|
89
|
+
#
|
90
|
+
# A third `prefix` argument can be pass along to be prepended to
|
91
|
+
# the string.
|
92
|
+
def path(logical_path, fingerprint = true, prefix = nil)
|
93
|
+
logger.warn "Sprockets::Environment#path is deprecated\n#{caller[0..2].join("\n")}"
|
94
|
+
if fingerprint && asset = find_asset(logical_path.to_s.sub(/^\//, ''))
|
95
|
+
url = asset.digest_path
|
96
|
+
else
|
97
|
+
url = logical_path
|
98
|
+
end
|
99
|
+
|
100
|
+
url = File.join(prefix, url) if prefix
|
101
|
+
url = "/#{url}" unless url =~ /^\//
|
102
|
+
|
103
|
+
url
|
104
|
+
end
|
105
|
+
|
106
|
+
# Deprecated: Similar to `path`, `url` returns a full url given a Rack `env`
|
107
|
+
# Hash and a `logical_path`.
|
108
|
+
def url(env, logical_path, fingerprint = true, prefix = nil)
|
109
|
+
logger.warn "Sprockets::Environment#url is deprecated\n#{caller[0..2].join("\n")}"
|
110
|
+
req = Rack::Request.new(env)
|
111
|
+
|
112
|
+
url = req.scheme + "://"
|
113
|
+
url << req.host
|
114
|
+
|
115
|
+
if req.scheme == "https" && req.port != 443 ||
|
116
|
+
req.scheme == "http" && req.port != 80
|
117
|
+
url << ":#{req.port}"
|
118
|
+
end
|
119
|
+
|
120
|
+
url << path(logical_path, fingerprint, prefix)
|
121
|
+
|
122
|
+
url
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def forbidden_request?(env)
|
127
|
+
# Prevent access to files elsewhere on the file system
|
128
|
+
#
|
129
|
+
# http://example.org/assets/../../../etc/passwd
|
130
|
+
#
|
131
|
+
env["PATH_INFO"].include?("..")
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns a 403 Forbidden response tuple
|
135
|
+
def forbidden_response
|
136
|
+
[ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns a 404 Not Found response tuple
|
140
|
+
def not_found_response
|
141
|
+
[ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns a JavaScript response that re-throws a Ruby exception
|
145
|
+
# in the browser
|
146
|
+
def javascript_exception_response(exception)
|
147
|
+
err = "#{exception.class.name}: #{exception.message}"
|
148
|
+
body = "throw Error(#{err.inspect})"
|
149
|
+
[ 200, { "Content-Type" => "application/javascript", "Content-Length" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns a CSS response that hides all elements on the page and
|
153
|
+
# displays the exception
|
154
|
+
def css_exception_response(exception)
|
155
|
+
message = "\n#{exception.class.name}: #{exception.message}"
|
156
|
+
backtrace = "\n #{exception.backtrace.first}"
|
157
|
+
|
158
|
+
body = <<-CSS
|
159
|
+
html {
|
160
|
+
padding: 18px 36px;
|
161
|
+
}
|
162
|
+
|
163
|
+
head {
|
164
|
+
display: block;
|
165
|
+
}
|
166
|
+
|
167
|
+
body {
|
168
|
+
margin: 0;
|
169
|
+
padding: 0;
|
170
|
+
}
|
171
|
+
|
172
|
+
body > * {
|
173
|
+
display: none !important;
|
174
|
+
}
|
175
|
+
|
176
|
+
head:after, body:before, body:after {
|
177
|
+
display: block !important;
|
178
|
+
}
|
179
|
+
|
180
|
+
head:after {
|
181
|
+
font-family: sans-serif;
|
182
|
+
font-size: large;
|
183
|
+
font-weight: bold;
|
184
|
+
content: "Error compiling CSS asset";
|
185
|
+
}
|
186
|
+
|
187
|
+
body:before, body:after {
|
188
|
+
font-family: monospace;
|
189
|
+
white-space: pre-wrap;
|
190
|
+
}
|
191
|
+
|
192
|
+
body:before {
|
193
|
+
font-weight: bold;
|
194
|
+
content: "#{escape_css_content(message)}";
|
195
|
+
}
|
196
|
+
|
197
|
+
body:after {
|
198
|
+
content: "#{escape_css_content(backtrace)}";
|
199
|
+
}
|
200
|
+
CSS
|
201
|
+
|
202
|
+
[ 200, { "Content-Type" => "text/css;charset=utf-8", "Content-Length" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
|
203
|
+
end
|
204
|
+
|
205
|
+
# Escape special characters for use inside a CSS content("...") string
|
206
|
+
def escape_css_content(content)
|
207
|
+
content.
|
208
|
+
gsub('\\', '\\\\005c ').
|
209
|
+
gsub("\n", '\\\\000a ').
|
210
|
+
gsub('"', '\\\\0022 ').
|
211
|
+
gsub('/', '\\\\002f ')
|
212
|
+
end
|
213
|
+
|
214
|
+
# Compare the requests `HTTP_IF_MODIFIED_SINCE` against the
|
215
|
+
# assets mtime
|
216
|
+
def not_modified?(asset, env)
|
217
|
+
env["HTTP_IF_MODIFIED_SINCE"] == asset.mtime.httpdate
|
218
|
+
end
|
219
|
+
|
220
|
+
# Compare the requests `HTTP_IF_NONE_MATCH` against the assets digest
|
221
|
+
def etag_match?(asset, env)
|
222
|
+
env["HTTP_IF_NONE_MATCH"] == etag(asset)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Test if `?body=1` or `body=true` query param is set
|
226
|
+
def body_only?(env)
|
227
|
+
env["QUERY_STRING"].to_s =~ /body=(1|t)/
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns a 304 Not Modified response tuple
|
231
|
+
def not_modified_response(asset, env)
|
232
|
+
[ 304, {}, [] ]
|
233
|
+
end
|
234
|
+
|
235
|
+
# Returns a 200 OK response tuple
|
236
|
+
def ok_response(asset, env)
|
237
|
+
if body_only?(env)
|
238
|
+
[ 200, headers(env, asset, Rack::Utils.bytesize(asset.body)), [asset.body] ]
|
239
|
+
else
|
240
|
+
[ 200, headers(env, asset, asset.length), asset ]
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def headers(env, asset, length)
|
245
|
+
Hash.new.tap do |headers|
|
246
|
+
# Set content type and length headers
|
247
|
+
headers["Content-Type"] = asset.content_type
|
248
|
+
headers["Content-Length"] = length.to_s
|
249
|
+
|
250
|
+
# Set caching headers
|
251
|
+
headers["Cache-Control"] = "public"
|
252
|
+
headers["Last-Modified"] = asset.mtime.httpdate
|
253
|
+
headers["ETag"] = etag(asset)
|
254
|
+
|
255
|
+
# If the request url contains a fingerprint, set a long
|
256
|
+
# expires on the response
|
257
|
+
if attributes_for(env["PATH_INFO"]).path_fingerprint
|
258
|
+
headers["Cache-Control"] << ", max-age=31536000"
|
259
|
+
|
260
|
+
# Otherwise set `must-revalidate` since the asset could be modified.
|
261
|
+
else
|
262
|
+
headers["Cache-Control"] << ", must-revalidate"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Helper to quote the assets digest for use as an ETag.
|
268
|
+
def etag(asset)
|
269
|
+
%("#{asset.digest}")
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'sprockets/asset'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
module Sprockets
|
6
|
+
# `StaticAsset`s are used for files that are served verbatim without
|
7
|
+
# any processing or concatenation. These are typical images and
|
8
|
+
# other binary files.
|
9
|
+
class StaticAsset < Asset
|
10
|
+
# Define extra attributes to be serialized.
|
11
|
+
def self.serialized_attributes
|
12
|
+
super + %w( content_type mtime length digest )
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(environment, logical_path, pathname, digest = nil)
|
16
|
+
super(environment, logical_path, pathname)
|
17
|
+
@digest = digest
|
18
|
+
load!
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns file contents as its `body`.
|
22
|
+
def body
|
23
|
+
# File is read everytime to avoid memory bloat of large binary files
|
24
|
+
pathname.open('rb') { |f| f.read }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Checks if Asset is fresh by comparing the actual mtime and
|
28
|
+
# digest to the inmemory model.
|
29
|
+
def fresh?
|
30
|
+
# Check current mtime and digest
|
31
|
+
dependency_fresh?('path' => pathname, 'mtime' => mtime, 'hexdigest' => digest)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Implemented for Rack SendFile support.
|
35
|
+
def to_path
|
36
|
+
pathname.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
# `to_s` is aliased to body since static assets can't have any dependencies.
|
40
|
+
def to_s
|
41
|
+
body
|
42
|
+
end
|
43
|
+
|
44
|
+
# Save asset to disk.
|
45
|
+
def write_to(filename, options = {})
|
46
|
+
# Gzip contents if filename has '.gz'
|
47
|
+
options[:compress] ||= File.extname(filename) == '.gz'
|
48
|
+
|
49
|
+
if options[:compress]
|
50
|
+
# Open file and run it through `Zlib`
|
51
|
+
pathname.open('rb') do |rd|
|
52
|
+
File.open("#{filename}+", 'wb') do |wr|
|
53
|
+
gz = Zlib::GzipWriter.new(wr, Zlib::BEST_COMPRESSION)
|
54
|
+
buf = ""
|
55
|
+
while rd.read(16384, buf)
|
56
|
+
gz.write(buf)
|
57
|
+
end
|
58
|
+
gz.close
|
59
|
+
end
|
60
|
+
end
|
61
|
+
else
|
62
|
+
# If no compression needs to be done, we can just copy it into place.
|
63
|
+
FileUtils.cp(pathname, "#{filename}+")
|
64
|
+
end
|
65
|
+
|
66
|
+
# Atomic write
|
67
|
+
FileUtils.mv("#{filename}+", filename)
|
68
|
+
|
69
|
+
# Set mtime correctly
|
70
|
+
File.utime(mtime, mtime, filename)
|
71
|
+
|
72
|
+
nil
|
73
|
+
ensure
|
74
|
+
# Ensure tmp file gets cleaned up
|
75
|
+
FileUtils.rm("#{filename}+") if File.exist?("#{filename}+")
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def load!
|
80
|
+
content_type
|
81
|
+
mtime
|
82
|
+
length
|
83
|
+
digest
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'sprockets/errors'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
# `Trail` is an internal mixin whose public methods are exposed on
|
6
|
+
# the `Environment` and `Index` classes.
|
7
|
+
module Trail
|
8
|
+
# Returns `Environment` root.
|
9
|
+
#
|
10
|
+
# All relative paths are expanded with root as its base. To be
|
11
|
+
# useful set this to your applications root directory. (`Rails.root`)
|
12
|
+
def root
|
13
|
+
trail.root.dup
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns an `Array` of path `String`s.
|
17
|
+
#
|
18
|
+
# These paths will be used for asset logical path lookups.
|
19
|
+
#
|
20
|
+
# Note that a copy of the `Array` is returned so mutating will
|
21
|
+
# have no affect on the environment. See `append_path`,
|
22
|
+
# `prepend_path`, and `clear_paths`.
|
23
|
+
def paths
|
24
|
+
trail.paths.dup
|
25
|
+
end
|
26
|
+
|
27
|
+
# Prepend a `path` to the `paths` list.
|
28
|
+
#
|
29
|
+
# Paths at the end of the `Array` have the least priority.
|
30
|
+
def prepend_path(path)
|
31
|
+
expire_index!
|
32
|
+
@trail.prepend_path(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Append a `path` to the `paths` list.
|
36
|
+
#
|
37
|
+
# Paths at the beginning of the `Array` have a higher priority.
|
38
|
+
def append_path(path)
|
39
|
+
expire_index!
|
40
|
+
@trail.append_path(path)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Clear all paths and start fresh.
|
44
|
+
#
|
45
|
+
# There is no mechanism for reordering paths, so its best to
|
46
|
+
# completely wipe the paths list and reappend them in the order
|
47
|
+
# you want.
|
48
|
+
def clear_paths
|
49
|
+
expire_index!
|
50
|
+
@trail.paths.dup.each { |path| @trail.remove_path(path) }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns an `Array` of extensions.
|
54
|
+
#
|
55
|
+
# These extensions maybe omitted from logical path searches.
|
56
|
+
#
|
57
|
+
# # => [".js", ".css", ".coffee", ".sass", ...]
|
58
|
+
#
|
59
|
+
def extensions
|
60
|
+
trail.extensions.dup
|
61
|
+
end
|
62
|
+
|
63
|
+
# Finds the expanded real path for a given logical path by
|
64
|
+
# searching the environment's paths.
|
65
|
+
#
|
66
|
+
# resolve("application.js")
|
67
|
+
# # => "/path/to/app/javascripts/application.js.coffee"
|
68
|
+
#
|
69
|
+
# A `FileNotFound` exception is raised if the file does not exist.
|
70
|
+
def resolve(logical_path, options = {})
|
71
|
+
# If a block is given, preform an iterable search
|
72
|
+
if block_given?
|
73
|
+
args = attributes_for(logical_path).search_paths + [options]
|
74
|
+
trail.find(*args) do |path|
|
75
|
+
yield Pathname.new(path)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
resolve(logical_path, options) do |pathname|
|
79
|
+
return pathname
|
80
|
+
end
|
81
|
+
raise FileNotFound, "couldn't find file '#{logical_path}'"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
def trail
|
87
|
+
@trail
|
88
|
+
end
|
89
|
+
|
90
|
+
def find_asset_in_path(logical_path, options = {})
|
91
|
+
# Strip fingerprint on logical path if there is one.
|
92
|
+
# Not sure how valuable this feature is...
|
93
|
+
if fingerprint = attributes_for(logical_path).path_fingerprint
|
94
|
+
pathname = resolve(logical_path.to_s.sub("-#{fingerprint}", ''))
|
95
|
+
else
|
96
|
+
pathname = resolve(logical_path)
|
97
|
+
end
|
98
|
+
rescue FileNotFound
|
99
|
+
nil
|
100
|
+
else
|
101
|
+
# Build the asset for the actual pathname
|
102
|
+
asset = build_asset(logical_path, pathname, options)
|
103
|
+
|
104
|
+
# Double check request fingerprint against actual digest
|
105
|
+
# Again, not sure if this code path is even reachable
|
106
|
+
if fingerprint && fingerprint != asset.digest
|
107
|
+
logger.error "Nonexistent asset #{logical_path} @ #{fingerprint}"
|
108
|
+
asset = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
asset
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|