sprockets 2.12.5 → 3.0.0.beta.1
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.
- checksums.yaml +5 -5
- data/LICENSE +2 -2
- data/README.md +61 -34
- data/lib/rake/sprocketstask.rb +5 -4
- data/lib/sprockets.rb +123 -85
- data/lib/sprockets/asset.rb +161 -200
- data/lib/sprockets/asset_uri.rb +64 -0
- data/lib/sprockets/base.rb +138 -373
- data/lib/sprockets/bower.rb +56 -0
- data/lib/sprockets/bundle.rb +32 -0
- data/lib/sprockets/cache.rb +220 -0
- data/lib/sprockets/cache/file_store.rb +145 -13
- data/lib/sprockets/cache/memory_store.rb +66 -0
- data/lib/sprockets/cache/null_store.rb +46 -0
- data/lib/sprockets/cached_environment.rb +103 -0
- data/lib/sprockets/closure_compressor.rb +30 -12
- data/lib/sprockets/coffee_script_template.rb +23 -0
- data/lib/sprockets/compressing.rb +20 -25
- data/lib/sprockets/configuration.rb +95 -0
- data/lib/sprockets/context.rb +68 -131
- data/lib/sprockets/directive_processor.rb +138 -179
- data/lib/sprockets/eco_template.rb +10 -19
- data/lib/sprockets/ejs_template.rb +10 -19
- data/lib/sprockets/encoding_utils.rb +246 -0
- data/lib/sprockets/engines.rb +40 -29
- data/lib/sprockets/environment.rb +10 -66
- data/lib/sprockets/erb_template.rb +23 -0
- data/lib/sprockets/errors.rb +5 -13
- data/lib/sprockets/http_utils.rb +97 -0
- data/lib/sprockets/jst_processor.rb +28 -15
- data/lib/sprockets/lazy_processor.rb +15 -0
- data/lib/sprockets/legacy.rb +23 -0
- data/lib/sprockets/legacy_proc_processor.rb +35 -0
- data/lib/sprockets/legacy_tilt_processor.rb +29 -0
- data/lib/sprockets/manifest.rb +128 -99
- data/lib/sprockets/mime.rb +114 -33
- data/lib/sprockets/path_utils.rb +179 -0
- data/lib/sprockets/paths.rb +13 -26
- data/lib/sprockets/processing.rb +198 -107
- data/lib/sprockets/resolve.rb +289 -0
- data/lib/sprockets/sass_compressor.rb +36 -17
- data/lib/sprockets/sass_template.rb +269 -46
- data/lib/sprockets/server.rb +113 -83
- data/lib/sprockets/transformers.rb +69 -0
- data/lib/sprockets/uglifier_compressor.rb +36 -15
- data/lib/sprockets/utils.rb +161 -44
- data/lib/sprockets/version.rb +1 -1
- data/lib/sprockets/yui_compressor.rb +37 -12
- metadata +64 -106
- data/lib/sprockets/asset_attributes.rb +0 -137
- data/lib/sprockets/bundled_asset.rb +0 -78
- data/lib/sprockets/caching.rb +0 -96
- data/lib/sprockets/charset_normalizer.rb +0 -41
- data/lib/sprockets/index.rb +0 -100
- data/lib/sprockets/processed_asset.rb +0 -152
- data/lib/sprockets/processor.rb +0 -32
- data/lib/sprockets/safety_colons.rb +0 -28
- data/lib/sprockets/sass_cache_store.rb +0 -29
- data/lib/sprockets/sass_functions.rb +0 -70
- data/lib/sprockets/sass_importer.rb +0 -30
- data/lib/sprockets/scss_template.rb +0 -13
- data/lib/sprockets/static_asset.rb +0 -60
data/lib/sprockets/server.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'time'
|
2
|
-
require '
|
2
|
+
require 'rack/utils'
|
3
3
|
|
4
4
|
module Sprockets
|
5
5
|
# `Server` is a concern mixed into `Environment` and
|
6
|
-
# `
|
6
|
+
# `CachedEnvironment` that provides a Rack compatible `call`
|
7
7
|
# interface and url generation helpers.
|
8
8
|
module Server
|
9
9
|
# `call` implements the Rack 1.x specification which accepts an
|
@@ -23,59 +23,84 @@ module Sprockets
|
|
23
23
|
start_time = Time.now.to_f
|
24
24
|
time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
|
25
25
|
|
26
|
-
|
26
|
+
if env['REQUEST_METHOD'] != 'GET'
|
27
|
+
return method_not_allowed_response
|
28
|
+
end
|
27
29
|
|
28
|
-
|
29
|
-
env['rack.session.options'] ||= {}
|
30
|
-
env['rack.session.options'][:defer] = true
|
31
|
-
env['rack.session.options'][:skip] = true
|
30
|
+
msg = "Served asset #{env['PATH_INFO']} -"
|
32
31
|
|
33
32
|
# Extract the path from everything after the leading slash
|
34
|
-
path = unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
|
35
|
-
|
36
|
-
# Strip fingerprint
|
37
|
-
if fingerprint = path_fingerprint(path)
|
38
|
-
path = path.sub("-#{fingerprint}", '')
|
39
|
-
end
|
33
|
+
path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
|
40
34
|
|
41
35
|
# URLs containing a `".."` are rejected for security reasons.
|
42
36
|
if forbidden_request?(path)
|
43
37
|
return forbidden_response
|
44
38
|
end
|
45
39
|
|
40
|
+
# Strip fingerprint
|
41
|
+
if fingerprint = path_fingerprint(path)
|
42
|
+
path = path.sub("-#{fingerprint}", '')
|
43
|
+
end
|
44
|
+
|
46
45
|
# Look up the asset.
|
47
|
-
|
46
|
+
options = {}
|
47
|
+
options[:bundle] = !body_only?(env)
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
if fingerprint
|
50
|
+
if_match = fingerprint
|
51
|
+
elsif env['HTTP_IF_MATCH']
|
52
|
+
if_match = env['HTTP_IF_MATCH'][/^"(\w+)"$/, 1]
|
53
|
+
end
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
+
if env['HTTP_IF_NONE_MATCH']
|
56
|
+
if_none_match = env['HTTP_IF_NONE_MATCH'][/^"(\w+)"$/, 1]
|
57
|
+
end
|
55
58
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
+
if !if_match && !if_none_match && env['HTTP_ACCEPT_ENCODING']
|
60
|
+
# Accept-Encoding negotiation is only enabled for non-fingerprinted
|
61
|
+
# assets. Avoids the "Apache ETag gzip" bug. Just Google it.
|
62
|
+
# https://issues.apache.org/bugzilla/show_bug.cgi?id=39727
|
63
|
+
options[:accept_encoding] = env['HTTP_ACCEPT_ENCODING']
|
64
|
+
end
|
59
65
|
|
60
|
-
|
61
|
-
not_modified_response(asset, env)
|
66
|
+
asset = find_asset(path, options)
|
62
67
|
|
68
|
+
if asset.nil?
|
69
|
+
status = :not_found
|
70
|
+
elsif fingerprint && asset.digest != fingerprint
|
71
|
+
status = :not_found
|
72
|
+
elsif if_match && asset.digest != if_match
|
73
|
+
status = :precondition_failed
|
74
|
+
elsif if_none_match && asset.digest == if_none_match
|
75
|
+
status = :not_modified
|
63
76
|
else
|
64
|
-
|
77
|
+
status = :ok
|
78
|
+
end
|
65
79
|
|
66
|
-
|
80
|
+
case status
|
81
|
+
when :ok
|
82
|
+
logger.info "#{msg} 200 OK (#{time_elapsed.call}ms)"
|
67
83
|
ok_response(asset, env)
|
84
|
+
when :not_modified
|
85
|
+
logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)"
|
86
|
+
not_modified_response(env, if_none_match)
|
87
|
+
when :not_found
|
88
|
+
logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
|
89
|
+
not_found_response
|
90
|
+
when :precondition_failed
|
91
|
+
logger.info "#{msg} 412 Precondition Failed (#{time_elapsed.call}ms)"
|
92
|
+
precondition_failed_response
|
68
93
|
end
|
69
94
|
rescue Exception => e
|
70
95
|
logger.error "Error compiling asset #{path}:"
|
71
96
|
logger.error "#{e.class.name}: #{e.message}"
|
72
97
|
|
73
|
-
case
|
74
|
-
when "
|
98
|
+
case File.extname(path)
|
99
|
+
when ".js"
|
75
100
|
# Re-throw JavaScript asset exceptions to the browser
|
76
101
|
logger.info "#{msg} 500 Internal Server Error\n\n"
|
77
102
|
return javascript_exception_response(e)
|
78
|
-
when "
|
103
|
+
when ".css"
|
79
104
|
# Display CSS asset exceptions in the browser
|
80
105
|
logger.info "#{msg} 500 Internal Server Error\n\n"
|
81
106
|
return css_exception_response(e)
|
@@ -90,7 +115,17 @@ module Sprockets
|
|
90
115
|
#
|
91
116
|
# http://example.org/assets/../../../etc/passwd
|
92
117
|
#
|
93
|
-
path.include?("..")
|
118
|
+
path.include?("..")
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns a 200 OK response tuple
|
122
|
+
def ok_response(asset, env)
|
123
|
+
[ 200, headers(env, asset, asset.length), asset ]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns a 304 Not Modified response tuple
|
127
|
+
def not_modified_response(env, digest)
|
128
|
+
[ 304, cache_headers(env, digest), [] ]
|
94
129
|
end
|
95
130
|
|
96
131
|
# Returns a 403 Forbidden response tuple
|
@@ -103,12 +138,20 @@ module Sprockets
|
|
103
138
|
[ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
|
104
139
|
end
|
105
140
|
|
141
|
+
def method_not_allowed_response
|
142
|
+
[ 405, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ]
|
143
|
+
end
|
144
|
+
|
145
|
+
def precondition_failed_response
|
146
|
+
[ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
|
147
|
+
end
|
148
|
+
|
106
149
|
# Returns a JavaScript response that re-throws a Ruby exception
|
107
150
|
# in the browser
|
108
151
|
def javascript_exception_response(exception)
|
109
|
-
err = "#{exception.class.name}: #{exception.message}"
|
152
|
+
err = "#{exception.class.name}: #{exception.message}\n (in #{exception.backtrace[0]})"
|
110
153
|
body = "throw Error(#{err.inspect})"
|
111
|
-
[ 200, { "Content-Type" => "application/javascript", "Content-Length" =>
|
154
|
+
[ 200, { "Content-Type" => "application/javascript", "Content-Length" => body.bytesize.to_s }, [ body ] ]
|
112
155
|
end
|
113
156
|
|
114
157
|
# Returns a CSS response that hides all elements on the page and
|
@@ -161,7 +204,7 @@ module Sprockets
|
|
161
204
|
}
|
162
205
|
CSS
|
163
206
|
|
164
|
-
[ 200, { "Content-Type" => "text/css;charset=utf-8", "Content-Length" =>
|
207
|
+
[ 200, { "Content-Type" => "text/css; charset=utf-8", "Content-Length" => body.bytesize.to_s }, [ body ] ]
|
165
208
|
end
|
166
209
|
|
167
210
|
# Escape special characters for use inside a CSS content("...") string
|
@@ -173,47 +216,53 @@ module Sprockets
|
|
173
216
|
gsub('/', '\\\\002f ')
|
174
217
|
end
|
175
218
|
|
176
|
-
# Compare the requests `HTTP_IF_NONE_MATCH` against the assets digest
|
177
|
-
def etag_match?(asset, env)
|
178
|
-
env["HTTP_IF_NONE_MATCH"] == etag(asset)
|
179
|
-
end
|
180
|
-
|
181
219
|
# Test if `?body=1` or `body=true` query param is set
|
182
220
|
def body_only?(env)
|
183
221
|
env["QUERY_STRING"].to_s =~ /body=(1|t)/
|
184
222
|
end
|
185
223
|
|
186
|
-
|
187
|
-
|
188
|
-
[ 304, {}, [] ]
|
189
|
-
end
|
224
|
+
def cache_headers(env, digest)
|
225
|
+
headers = {}
|
190
226
|
|
191
|
-
|
192
|
-
|
193
|
-
[
|
227
|
+
# Set caching headers
|
228
|
+
headers["Cache-Control"] = "public"
|
229
|
+
headers["ETag"] = %("#{digest}")
|
230
|
+
|
231
|
+
# If the request url contains a fingerprint, set a long
|
232
|
+
# expires on the response
|
233
|
+
if path_fingerprint(env["PATH_INFO"])
|
234
|
+
headers["Cache-Control"] << ", max-age=31536000"
|
235
|
+
|
236
|
+
# Otherwise set `must-revalidate` since the asset could be modified.
|
237
|
+
else
|
238
|
+
headers["Cache-Control"] << ", must-revalidate"
|
239
|
+
headers["Vary"] = "Accept-Encoding"
|
240
|
+
end
|
241
|
+
|
242
|
+
headers
|
194
243
|
end
|
195
244
|
|
196
245
|
def headers(env, asset, length)
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
# Otherwise set `must-revalidate` since the asset could be modified.
|
213
|
-
else
|
214
|
-
headers["Cache-Control"] << ", must-revalidate"
|
246
|
+
headers = {}
|
247
|
+
|
248
|
+
# Set content encoding
|
249
|
+
if asset.encoding
|
250
|
+
headers["Content-Encoding"] = asset.encoding
|
251
|
+
end
|
252
|
+
|
253
|
+
# Set content length header
|
254
|
+
headers["Content-Length"] = length.to_s
|
255
|
+
|
256
|
+
# Set content type header
|
257
|
+
if type = asset.content_type
|
258
|
+
# Set charset param for text/* mime types
|
259
|
+
if type.start_with?("text/") && asset.charset
|
260
|
+
type += "; charset=#{asset.charset}"
|
215
261
|
end
|
262
|
+
headers["Content-Type"] = type
|
216
263
|
end
|
264
|
+
|
265
|
+
headers.merge(cache_headers(env, asset.digest))
|
217
266
|
end
|
218
267
|
|
219
268
|
# Gets digest fingerprint.
|
@@ -222,26 +271,7 @@ module Sprockets
|
|
222
271
|
# # => "0aa2105d29558f3eb790d411d7d8fb66"
|
223
272
|
#
|
224
273
|
def path_fingerprint(path)
|
225
|
-
path[/-([0-9a-f]{7,40})\.[^.]
|
226
|
-
end
|
227
|
-
|
228
|
-
# URI.unescape is deprecated on 1.9. We need to use URI::Parser
|
229
|
-
# if its available.
|
230
|
-
if defined? URI::DEFAULT_PARSER
|
231
|
-
def unescape(str)
|
232
|
-
str = URI::DEFAULT_PARSER.unescape(str)
|
233
|
-
str.force_encoding(Encoding.default_internal) if Encoding.default_internal
|
234
|
-
str
|
235
|
-
end
|
236
|
-
else
|
237
|
-
def unescape(str)
|
238
|
-
URI.unescape(str)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
# Helper to quote the assets digest for use as an ETag.
|
243
|
-
def etag(asset)
|
244
|
-
%("#{asset.digest}")
|
274
|
+
path[/-([0-9a-f]{7,40})\.[^.]+$/, 1]
|
245
275
|
end
|
246
276
|
end
|
247
277
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Sprockets
|
2
|
+
module Transformers
|
3
|
+
# Public: Two level mapping of a source mime type to a target mime type.
|
4
|
+
#
|
5
|
+
# environment.transformers
|
6
|
+
# # => { 'text/coffeescript' => {
|
7
|
+
# 'application/javascript' => ConvertCoffeeScriptToJavaScript
|
8
|
+
# }
|
9
|
+
# }
|
10
|
+
#
|
11
|
+
attr_reader :transformers
|
12
|
+
|
13
|
+
# Public: Register a transformer from and to a mime type.
|
14
|
+
#
|
15
|
+
# from - String mime type
|
16
|
+
# to - String mime type
|
17
|
+
# proc - Callable block that accepts an input Hash.
|
18
|
+
#
|
19
|
+
# Examples
|
20
|
+
#
|
21
|
+
# register_transformer 'text/coffeescript', 'application/javascript',
|
22
|
+
# ConvertCoffeeScriptToJavaScript
|
23
|
+
#
|
24
|
+
# register_transformer 'image/svg+xml', 'image/png', ConvertSvgToPng
|
25
|
+
#
|
26
|
+
# Returns nothing.
|
27
|
+
def register_transformer(from, to, proc)
|
28
|
+
mutate_hash_config(:transformers, from) do |transformers|
|
29
|
+
transformers.merge(to => proc)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Resolve target mime type that the source type should be
|
34
|
+
# transformed to.
|
35
|
+
#
|
36
|
+
# type - String from mime type
|
37
|
+
# accept - String accept type list (default: '*/*')
|
38
|
+
#
|
39
|
+
# Examples
|
40
|
+
#
|
41
|
+
# resolve_transform_type('text/plain', 'text/plain')
|
42
|
+
# # => 'text/plain'
|
43
|
+
#
|
44
|
+
# resolve_transform_type('image/svg+xml', 'image/png, image/*')
|
45
|
+
# # => 'image/png'
|
46
|
+
#
|
47
|
+
# resolve_transform_type('text/css', 'image/png')
|
48
|
+
# # => nil
|
49
|
+
#
|
50
|
+
# Returns String mime type or nil is no type satisfied the accept value.
|
51
|
+
def resolve_transform_type(type, accept = nil)
|
52
|
+
find_best_mime_type_match(accept || '*/*', [type].compact + transformers[type].keys)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Internal: Find and load transformer by from and to mime type.
|
56
|
+
#
|
57
|
+
# from - String mime type
|
58
|
+
# to - String mime type
|
59
|
+
#
|
60
|
+
# Returns Array of Procs.
|
61
|
+
def unwrap_transformer(from, to)
|
62
|
+
if processor = transformers[from][to]
|
63
|
+
[unwrap_processor(processor)]
|
64
|
+
else
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,28 +1,49 @@
|
|
1
|
-
require '
|
1
|
+
require 'uglifier'
|
2
2
|
|
3
3
|
module Sprockets
|
4
|
-
|
5
|
-
|
4
|
+
# Public: Uglifier/Uglify compressor.
|
5
|
+
#
|
6
|
+
# To accept the default options
|
7
|
+
#
|
8
|
+
# environment.register_bundle_processor 'application/javascript',
|
9
|
+
# Sprockets::UglifierCompressor
|
10
|
+
#
|
11
|
+
# Or to pass options to the Uglifier class.
|
12
|
+
#
|
13
|
+
# environment.register_bundle_processor 'application/javascript',
|
14
|
+
# Sprockets::UglifierCompressor.new(comments: :copyright)
|
15
|
+
#
|
16
|
+
class UglifierCompressor
|
17
|
+
VERSION = '1'
|
6
18
|
|
7
|
-
def self.
|
8
|
-
|
19
|
+
def self.call(*args)
|
20
|
+
new.call(*args)
|
9
21
|
end
|
10
22
|
|
11
|
-
def
|
12
|
-
require_template_library 'uglifier'
|
13
|
-
end
|
14
|
-
|
15
|
-
def prepare
|
16
|
-
end
|
17
|
-
|
18
|
-
def evaluate(context, locals, &block)
|
23
|
+
def initialize(options = {})
|
19
24
|
# Feature detect Uglifier 2.0 option support
|
20
25
|
if Uglifier::DEFAULTS[:copyright]
|
21
26
|
# Uglifier < 2.x
|
22
|
-
|
27
|
+
options[:copyright] ||= false
|
23
28
|
else
|
24
29
|
# Uglifier >= 2.x
|
25
|
-
|
30
|
+
options[:copyright] ||= :none
|
31
|
+
end
|
32
|
+
|
33
|
+
@uglifier = ::Uglifier.new(options)
|
34
|
+
|
35
|
+
@cache_key = [
|
36
|
+
'UglifierCompressor',
|
37
|
+
::Uglifier::VERSION,
|
38
|
+
VERSION,
|
39
|
+
options
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(input)
|
44
|
+
data = input[:data]
|
45
|
+
input[:cache].fetch(@cache_key + [data]) do
|
46
|
+
@uglifier.compile(data)
|
26
47
|
end
|
27
48
|
end
|
28
49
|
end
|
data/lib/sprockets/utils.rb
CHANGED
@@ -1,55 +1,48 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'set'
|
3
|
+
|
1
4
|
module Sprockets
|
2
5
|
# `Utils`, we didn't know where else to put it!
|
3
6
|
module Utils
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
26
|
-
end
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# Internal: Check if string has a trailing semicolon.
|
10
|
+
#
|
11
|
+
# str - String
|
12
|
+
#
|
13
|
+
# Returns true or false.
|
14
|
+
def string_end_with_semicolon?(str)
|
15
|
+
i = str.size - 1
|
16
|
+
while i >= 0
|
17
|
+
c = str[i]
|
18
|
+
i -= 1
|
19
|
+
if c == "\n" || c == " " || c == "\t"
|
20
|
+
next
|
21
|
+
elsif c != ";"
|
22
|
+
return false
|
23
|
+
else
|
24
|
+
return true
|
27
25
|
end
|
28
26
|
end
|
27
|
+
true
|
28
|
+
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
# If we find a UTF-16 BOM, theres nothing we can do on
|
43
|
-
# 1.8. Only UTF-8 is supported.
|
44
|
-
elsif data =~ UTF16_BOM_PATTERN
|
45
|
-
raise EncodingError, "#{pathname} has a UTF-16 BOM. " +
|
46
|
-
"Resave the file as UTF-8 or upgrade to Ruby 1.9."
|
47
|
-
end
|
48
|
-
end
|
30
|
+
# Internal: Accumulate asset source to buffer and append a trailing
|
31
|
+
# semicolon if necessary.
|
32
|
+
#
|
33
|
+
# buf - String memo
|
34
|
+
# asset - Asset
|
35
|
+
#
|
36
|
+
# Returns appended buffer String.
|
37
|
+
def concat_javascript_sources(buf, source)
|
38
|
+
if string_end_with_semicolon?(buf)
|
39
|
+
buf + source
|
40
|
+
else
|
41
|
+
buf + ";\n" + source
|
49
42
|
end
|
50
43
|
end
|
51
44
|
|
52
|
-
# Prepends a leading "." to an extension if its missing.
|
45
|
+
# Internal: Prepends a leading "." to an extension if its missing.
|
53
46
|
#
|
54
47
|
# normalize_extension("js")
|
55
48
|
# # => ".js"
|
@@ -57,7 +50,7 @@ module Sprockets
|
|
57
50
|
# normalize_extension(".css")
|
58
51
|
# # => ".css"
|
59
52
|
#
|
60
|
-
def
|
53
|
+
def normalize_extension(extension)
|
61
54
|
extension = extension.to_s
|
62
55
|
if extension[/^\./]
|
63
56
|
extension
|
@@ -65,5 +58,129 @@ module Sprockets
|
|
65
58
|
".#{extension}"
|
66
59
|
end
|
67
60
|
end
|
61
|
+
|
62
|
+
# Internal: Feature detect if UnboundMethods can #bind to any Object or
|
63
|
+
# just Objects that share the same super class.
|
64
|
+
# Basically if RUBY_VERSION >= 2.
|
65
|
+
UNBOUND_METHODS_BIND_TO_ANY_OBJECT = begin
|
66
|
+
foo = Module.new { def bar; end }
|
67
|
+
foo.instance_method(:bar).bind(Object.new)
|
68
|
+
true
|
69
|
+
rescue TypeError
|
70
|
+
false
|
71
|
+
end
|
72
|
+
|
73
|
+
# Internal: Inject into target module for the duration of the block.
|
74
|
+
#
|
75
|
+
# mod - Module
|
76
|
+
#
|
77
|
+
# Returns result of block.
|
78
|
+
def module_include(base, mod)
|
79
|
+
old_methods = {}
|
80
|
+
|
81
|
+
mod.instance_methods.each do |sym|
|
82
|
+
old_methods[sym] = base.instance_method(sym) if base.method_defined?(sym)
|
83
|
+
end
|
84
|
+
|
85
|
+
unless UNBOUND_METHODS_BIND_TO_ANY_OBJECT
|
86
|
+
base.send(:include, mod) unless base < mod
|
87
|
+
end
|
88
|
+
|
89
|
+
mod.instance_methods.each do |sym|
|
90
|
+
method = mod.instance_method(sym)
|
91
|
+
base.send(:define_method, sym, method)
|
92
|
+
end
|
93
|
+
|
94
|
+
yield
|
95
|
+
ensure
|
96
|
+
mod.instance_methods.each do |sym|
|
97
|
+
base.send(:undef_method, sym) if base.method_defined?(sym)
|
98
|
+
end
|
99
|
+
old_methods.each do |sym, method|
|
100
|
+
base.send(:define_method, sym, method)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Internal: Generate a hexdigest for a nested JSON serializable object.
|
105
|
+
#
|
106
|
+
# obj - A JSON serializable object.
|
107
|
+
#
|
108
|
+
# Returns a String SHA1 digest of the object.
|
109
|
+
def hexdigest(obj)
|
110
|
+
digest = Digest::SHA1.new
|
111
|
+
queue = [obj]
|
112
|
+
|
113
|
+
while queue.length > 0
|
114
|
+
obj = queue.shift
|
115
|
+
klass = obj.class
|
116
|
+
|
117
|
+
if klass == String
|
118
|
+
digest << 'String'
|
119
|
+
digest << obj
|
120
|
+
elsif klass == Symbol
|
121
|
+
digest << 'Symbol'
|
122
|
+
digest << obj.to_s
|
123
|
+
elsif klass == Fixnum
|
124
|
+
digest << 'Fixnum'
|
125
|
+
digest << obj.to_s
|
126
|
+
elsif klass == TrueClass
|
127
|
+
digest << 'TrueClass'
|
128
|
+
elsif klass == FalseClass
|
129
|
+
digest << 'FalseClass'
|
130
|
+
elsif klass == NilClass
|
131
|
+
digest << 'NilClass'
|
132
|
+
elsif klass == Array
|
133
|
+
digest << 'Array'
|
134
|
+
queue.concat(obj)
|
135
|
+
elsif klass == Hash
|
136
|
+
digest << 'Hash'
|
137
|
+
queue.concat(obj.sort)
|
138
|
+
elsif klass == Set
|
139
|
+
digest << 'Set'
|
140
|
+
queue.concat(obj.to_a)
|
141
|
+
elsif klass == Encoding
|
142
|
+
digest << 'Encoding'
|
143
|
+
digest << obj.name
|
144
|
+
else
|
145
|
+
raise TypeError, "couldn't digest #{klass}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
digest.hexdigest
|
150
|
+
end
|
151
|
+
|
152
|
+
# Internal: Post-order Depth-First search algorithm.
|
153
|
+
#
|
154
|
+
# Used for resolving asset dependencies.
|
155
|
+
#
|
156
|
+
# initial - Initial Array of nodes to traverse.
|
157
|
+
# block -
|
158
|
+
# node - Current node to get children of
|
159
|
+
#
|
160
|
+
# Returns a Set of nodes.
|
161
|
+
def dfs(initial)
|
162
|
+
nodes, seen = Set.new, Set.new
|
163
|
+
stack = Array(initial).reverse
|
164
|
+
|
165
|
+
while node = stack.pop
|
166
|
+
if seen.include?(node)
|
167
|
+
nodes.add(node)
|
168
|
+
else
|
169
|
+
seen.add(node)
|
170
|
+
stack.push(node)
|
171
|
+
stack.concat(Array(yield node).reverse)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
nodes
|
176
|
+
end
|
177
|
+
|
178
|
+
def benchmark_start
|
179
|
+
Time.now.to_f
|
180
|
+
end
|
181
|
+
|
182
|
+
def benchmark_end(start_time)
|
183
|
+
((Time.now.to_f - start_time) * 1000).to_i
|
184
|
+
end
|
68
185
|
end
|
69
186
|
end
|