bpm 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitmodules +3 -0
- data/Gemfile +0 -13
- data/TODO.md +1 -0
- data/bpm.gemspec +8 -3
- data/lib/bpm/cli/base.rb +22 -20
- data/lib/bpm/generator.rb +3 -3
- data/lib/bpm/libgems_ext/dependency_installer.rb +4 -4
- data/lib/bpm/libgems_ext/installer.rb +1 -1
- data/lib/bpm/package.rb +13 -9
- data/lib/bpm/pipeline/generated_asset.rb +30 -3
- data/lib/bpm/pipeline/transport_processor.rb +20 -12
- data/lib/bpm/pipeline.rb +17 -9
- data/lib/bpm/project.rb +42 -10
- data/lib/bpm/server.rb +5 -0
- data/lib/bpm/version.rb +1 -1
- data/lib/bpm.rb +2 -0
- data/lib/vendored_sprockets.rb +7 -0
- data/spec/cli/add_spec.rb +40 -37
- data/spec/cli/build_spec.rb +5 -5
- data/spec/cli/new_spec.rb +49 -23
- data/spec/cli/push_spec.rb +5 -5
- data/spec/cli/unpack_spec.rb +14 -14
- data/spec/fixtures/{badrake-0.8.7.spd → badrake-0.8.7.bpkg} +0 -0
- data/spec/fixtures/{builder-3.0.0.spd → builder-3.0.0.bpkg} +0 -0
- data/spec/fixtures/{bundler-1.1.pre.spd → bundler-1.1.pre.bpkg} +0 -0
- data/spec/fixtures/{coffee-1.0.1.pre.spd → coffee-1.0.1.pre.bpkg} +0 -0
- data/spec/fixtures/{core-test-0.4.9.spd → core-test-0.4.9.bpkg} +0 -0
- data/spec/fixtures/{custom_generator-1.0.spd → custom_generator-1.0.bpkg} +0 -0
- data/spec/fixtures/custom_name/MyProject.json +4 -0
- data/spec/fixtures/hello_world/css/dummy.css +3 -0
- data/spec/fixtures/{highline-1.6.1.spd → highline-1.6.1.bpkg} +0 -0
- data/spec/fixtures/{ivory-0.0.1.spd → ivory-0.0.1.bpkg} +0 -0
- data/spec/fixtures/{jquery-1.4.3.spd → jquery-1.4.3.bpkg} +0 -0
- data/spec/fixtures/minitest/assets/bpm_packages.js +1 -0
- data/spec/fixtures/minitest/assets/bpm_styles.css +0 -0
- data/spec/fixtures/minitest/assets/minitest/app_package.js +1 -0
- data/spec/fixtures/minitest/lib/main.js +1 -0
- data/spec/fixtures/minitest/minitest.json +22 -0
- data/spec/fixtures/minitest/packages/uglyduck/lib/main.js +1 -0
- data/spec/fixtures/minitest/packages/uglyduck/minifier/main.js +4 -0
- data/spec/fixtures/minitest/packages/uglyduck/package.json +21 -0
- data/spec/fixtures/{optparse-1.0.1.spd → optparse-1.0.1.bpkg} +0 -0
- data/spec/fixtures/{rake-0.8.6.spd → rake-0.8.6.bpkg} +0 -0
- data/spec/fixtures/{rake-0.8.7.spd → rake-0.8.7.bpkg} +0 -0
- data/spec/fixtures/{spade-0.5.0.spd → spade-0.5.0.bpkg} +0 -0
- data/spec/fixtures/transporter/packages/transport/lib/main.js +1 -0
- data/spec/fixtures/transporter/packages/transport/package.json +1 -1
- data/spec/fixtures/transporter/packages/transport/transports/wrapper.js +5 -0
- data/spec/gauntlet_spec.rb +2 -2
- data/spec/package_spec.rb +3 -3
- data/spec/pipeline_spec.rb +175 -54
- data/spec/project_spec.rb +19 -3
- data/spec/support/fake_gem_server.rb +4 -4
- data/templates/init/project.json +3 -1
- data/templates/project/index.html +1 -1
- data/vendor/sprockets/.gitignore +7 -0
- data/vendor/sprockets/.travis.yml +6 -0
- data/vendor/sprockets/Gemfile +8 -0
- data/vendor/sprockets/LICENSE +20 -0
- data/vendor/sprockets/README.md +22 -0
- data/vendor/sprockets/Rakefile +8 -0
- data/vendor/sprockets/lib/sprockets/asset.rb +203 -0
- data/vendor/sprockets/lib/sprockets/asset_attributes.rb +161 -0
- data/vendor/sprockets/lib/sprockets/base.rb +147 -0
- data/vendor/sprockets/lib/sprockets/bundled_asset.rb +222 -0
- data/vendor/sprockets/lib/sprockets/cache/file_store.rb +41 -0
- data/vendor/sprockets/lib/sprockets/caching.rb +121 -0
- data/vendor/sprockets/lib/sprockets/charset_normalizer.rb +41 -0
- data/vendor/sprockets/lib/sprockets/context.rb +191 -0
- data/vendor/sprockets/lib/sprockets/digest.rb +73 -0
- data/vendor/sprockets/lib/sprockets/directive_processor.rb +380 -0
- data/vendor/sprockets/lib/sprockets/eco_template.rb +39 -0
- data/vendor/sprockets/lib/sprockets/ejs_template.rb +38 -0
- data/vendor/sprockets/lib/sprockets/engines.rb +92 -0
- data/vendor/sprockets/lib/sprockets/environment.rb +93 -0
- data/vendor/sprockets/lib/sprockets/errors.rb +17 -0
- data/vendor/sprockets/lib/sprockets/index.rb +80 -0
- data/vendor/sprockets/lib/sprockets/jst_processor.rb +26 -0
- data/vendor/sprockets/lib/sprockets/processing.rb +310 -0
- data/vendor/sprockets/lib/sprockets/processor.rb +32 -0
- data/vendor/sprockets/lib/sprockets/safety_colons.rb +28 -0
- data/vendor/sprockets/lib/sprockets/server.rb +270 -0
- data/vendor/sprockets/lib/sprockets/static_asset.rb +87 -0
- data/vendor/sprockets/lib/sprockets/static_compilation.rb +82 -0
- data/vendor/sprockets/lib/sprockets/trail.rb +122 -0
- data/vendor/sprockets/lib/sprockets/utils.rb +67 -0
- data/vendor/sprockets/lib/sprockets/version.rb +3 -0
- data/vendor/sprockets/lib/sprockets.rb +31 -0
- data/vendor/sprockets/sprockets.gemspec +30 -0
- data/vendor/sprockets/test/fixtures/asset/POW.png +0 -0
- data/vendor/sprockets/test/fixtures/asset/application.js +6 -0
- data/vendor/sprockets/test/fixtures/asset/bar-utf8.css +2 -0
- data/vendor/sprockets/test/fixtures/asset/charset.css +2 -0
- data/vendor/sprockets/test/fixtures/asset/circle/a.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/circle/b.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/circle/c.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/compat.js +4 -0
- data/vendor/sprockets/test/fixtures/asset/constants.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/constants.yml +1 -0
- data/vendor/sprockets/test/fixtures/asset/default_mime_type.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/filename.js.erb +1 -0
- data/vendor/sprockets/test/fixtures/asset/foo-utf8.css +2 -0
- data/vendor/sprockets/test/fixtures/asset/included_header.js +4 -0
- data/vendor/sprockets/test/fixtures/asset/jquery.tmpl.min.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/mismatch.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/multiple.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/multipleengine.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/noengine.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/noformat.coffee +1 -0
- data/vendor/sprockets/test/fixtures/asset/one.css +1 -0
- data/vendor/sprockets/test/fixtures/asset/oneengine.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/project.css +1 -0
- data/vendor/sprockets/test/fixtures/asset/project.js.erb +4 -0
- data/vendor/sprockets/test/fixtures/asset/relative/include.js +4 -0
- data/vendor/sprockets/test/fixtures/asset/relative/require.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/relative/require_outside_path.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/require_self.css +9 -0
- data/vendor/sprockets/test/fixtures/asset/require_self_twice.css +8 -0
- data/vendor/sprockets/test/fixtures/asset/semicolons/bar.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/semicolons/index.js +5 -0
- data/vendor/sprockets/test/fixtures/asset/sprite.css.erb +12 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all/b/c/d.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all/b/c/e.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all/b/c.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all/b.css +2 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all/b.js.erb +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all/d/c.js.coffee +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all/d/e.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all_with_require.js +7 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all_with_require_directory.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/all_with_require_tree.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/tree/directory/application.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/tree/directory/bar.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/directory/foo.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/tree/application.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/tree/tree/bar.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/tree/foo.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/with_logical_path/a/a.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/with_logical_path/require_tree_with_logical_path.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/without_argument/a.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/without_argument/b.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/tree/without_argument/require_tree_without_argument.js +1 -0
- data/vendor/sprockets/test/fixtures/asset/two.css +1 -0
- data/vendor/sprockets/test/fixtures/asset/unicode.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/unknownexts.min.js +2 -0
- data/vendor/sprockets/test/fixtures/asset/users.js.erb.str +4 -0
- data/vendor/sprockets/test/fixtures/context/POW.png +0 -0
- data/vendor/sprockets/test/fixtures/context/application.js.yml +3 -0
- data/vendor/sprockets/test/fixtures/context/bar.js +1 -0
- data/vendor/sprockets/test/fixtures/context/foo.js +2 -0
- data/vendor/sprockets/test/fixtures/context/helpers.css.erb +3 -0
- data/vendor/sprockets/test/fixtures/context/properties.js.erb +7 -0
- data/vendor/sprockets/test/fixtures/context/require_glob.js +1 -0
- data/vendor/sprockets/test/fixtures/context/resolve_content_type.js.erb +4 -0
- data/vendor/sprockets/test/fixtures/context/sprite.css.embed +3 -0
- data/vendor/sprockets/test/fixtures/default/application.js.coffee +4 -0
- data/vendor/sprockets/test/fixtures/default/coffee/foo.coffee +1 -0
- data/vendor/sprockets/test/fixtures/default/coffee/index.js +3 -0
- data/vendor/sprockets/test/fixtures/default/empty +0 -0
- data/vendor/sprockets/test/fixtures/default/gallery.css.erb +3 -0
- data/vendor/sprockets/test/fixtures/default/gallery.js +1 -0
- data/vendor/sprockets/test/fixtures/default/goodbye.jst.eco +1 -0
- data/vendor/sprockets/test/fixtures/default/hello.jst.ejs +1 -0
- data/vendor/sprockets/test/fixtures/default/hello.txt +1 -0
- data/vendor/sprockets/test/fixtures/default/interpolation.js +1 -0
- data/vendor/sprockets/test/fixtures/default/missing_require.js +1 -0
- data/vendor/sprockets/test/fixtures/default/mobile/a.js +1 -0
- data/vendor/sprockets/test/fixtures/default/mobile/b.js +1 -0
- data/vendor/sprockets/test/fixtures/default/mobile/c.css +1 -0
- data/vendor/sprockets/test/fixtures/default/mobile/d.css +1 -0
- data/vendor/sprockets/test/fixtures/default/mobile/index.css +3 -0
- data/vendor/sprockets/test/fixtures/default/mobile/index.js +1 -0
- data/vendor/sprockets/test/fixtures/default/noreturn.js +1 -0
- data/vendor/sprockets/test/fixtures/default/project.js.coffee.erb +2 -0
- data/vendor/sprockets/test/fixtures/directives/code_before_comment +3 -0
- data/vendor/sprockets/test/fixtures/directives/comment_without_directives +6 -0
- data/vendor/sprockets/test/fixtures/directives/directive_word_splitting +6 -0
- data/vendor/sprockets/test/fixtures/directives/directives_after_header +16 -0
- data/vendor/sprockets/test/fixtures/directives/double_slash +9 -0
- data/vendor/sprockets/test/fixtures/directives/hash +8 -0
- data/vendor/sprockets/test/fixtures/directives/no_header +2 -0
- data/vendor/sprockets/test/fixtures/directives/slash_star +10 -0
- data/vendor/sprockets/test/fixtures/directives/slash_star_single +4 -0
- data/vendor/sprockets/test/fixtures/directives/space_between_directive_word +2 -0
- data/vendor/sprockets/test/fixtures/directives/triple_hash +10 -0
- data/vendor/sprockets/test/fixtures/encoding/ascii.js +1 -0
- data/vendor/sprockets/test/fixtures/encoding/ascii_utf8.js +2 -0
- data/vendor/sprockets/test/fixtures/encoding/utf16.js +0 -0
- data/vendor/sprockets/test/fixtures/encoding/utf8.js +1 -0
- data/vendor/sprockets/test/fixtures/encoding/utf8_bom.js +1 -0
- data/vendor/sprockets/test/fixtures/engines/hello.alert +1 -0
- data/vendor/sprockets/test/fixtures/engines/moo.js.str +1 -0
- data/vendor/sprockets/test/fixtures/public/compiled-digest-0aa2105d29558f3eb790d411d7d8fb66.js +3 -0
- data/vendor/sprockets/test/fixtures/public/compiled-digest-1c41eb0cf934a0c76babe875f982f9d1.js +1 -0
- data/vendor/sprockets/test/fixtures/server/app/javascripts/application.js +5 -0
- data/vendor/sprockets/test/fixtures/server/app/javascripts/bar.js +1 -0
- data/vendor/sprockets/test/fixtures/server/app/javascripts/foo.js +1 -0
- data/vendor/sprockets/test/fixtures/server/app/javascripts/hello.txt +2 -0
- data/vendor/sprockets/test/fixtures/server/app/javascripts/tree.js +1 -0
- data/vendor/sprockets/test/fixtures/server/vendor/javascripts/missing_require.js +1 -0
- data/vendor/sprockets/test/fixtures/server/vendor/stylesheets/missing_require.css +1 -0
- data/vendor/sprockets/test/sprockets_test.rb +56 -0
- data/vendor/sprockets/test/test_asset.rb +593 -0
- data/vendor/sprockets/test/test_asset_attributes.rb +86 -0
- data/vendor/sprockets/test/test_caching.rb +62 -0
- data/vendor/sprockets/test/test_context.rb +115 -0
- data/vendor/sprockets/test/test_directive_processor.rb +124 -0
- data/vendor/sprockets/test/test_encoding.rb +65 -0
- data/vendor/sprockets/test/test_engines.rb +73 -0
- data/vendor/sprockets/test/test_environment.rb +610 -0
- data/vendor/sprockets/test/test_server.rb +227 -0
- metadata +258 -54
- data/spec/fixtures/transporter/packages/transport/lib/wrapper.js +0 -5
@@ -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,270 @@
|
|
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
|
+
# `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
|
+
if fingerprint && asset = find_asset(logical_path.to_s.sub(/^\//, ''))
|
94
|
+
url = attributes_for(logical_path).path_with_fingerprint(asset.digest)
|
95
|
+
else
|
96
|
+
url = logical_path
|
97
|
+
end
|
98
|
+
|
99
|
+
url = File.join(prefix, url) if prefix
|
100
|
+
url = "/#{url}" unless url =~ /^\//
|
101
|
+
|
102
|
+
url
|
103
|
+
end
|
104
|
+
|
105
|
+
# Similar to `path`, `url` returns a full url given a Rack `env`
|
106
|
+
# Hash and a `logical_path`.
|
107
|
+
def url(env, logical_path, fingerprint = true, prefix = nil)
|
108
|
+
req = Rack::Request.new(env)
|
109
|
+
|
110
|
+
url = req.scheme + "://"
|
111
|
+
url << req.host
|
112
|
+
|
113
|
+
if req.scheme == "https" && req.port != 443 ||
|
114
|
+
req.scheme == "http" && req.port != 80
|
115
|
+
url << ":#{req.port}"
|
116
|
+
end
|
117
|
+
|
118
|
+
url << path(logical_path, fingerprint, prefix)
|
119
|
+
|
120
|
+
url
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
def forbidden_request?(env)
|
125
|
+
# Prevent access to files elsewhere on the file system
|
126
|
+
#
|
127
|
+
# http://example.org/assets/../../../etc/passwd
|
128
|
+
#
|
129
|
+
env["PATH_INFO"].include?("..")
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns a 403 Forbidden response tuple
|
133
|
+
def forbidden_response
|
134
|
+
[ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns a 404 Not Found response tuple
|
138
|
+
def not_found_response
|
139
|
+
[ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns a JavaScript response that re-throws a Ruby exception
|
143
|
+
# in the browser
|
144
|
+
def javascript_exception_response(exception)
|
145
|
+
err = "#{exception.class.name}: #{exception.message}"
|
146
|
+
body = "throw Error(#{err.inspect})"
|
147
|
+
[ 200, { "Content-Type" => "application/javascript", "Content-Length" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns a CSS response that hides all elements on the page and
|
151
|
+
# displays the exception
|
152
|
+
def css_exception_response(exception)
|
153
|
+
message = "\n#{exception.class.name}: #{exception.message}"
|
154
|
+
backtrace = "\n #{exception.backtrace.first}"
|
155
|
+
|
156
|
+
body = <<-CSS
|
157
|
+
html {
|
158
|
+
padding: 18px 36px;
|
159
|
+
}
|
160
|
+
|
161
|
+
head {
|
162
|
+
display: block;
|
163
|
+
}
|
164
|
+
|
165
|
+
body {
|
166
|
+
margin: 0;
|
167
|
+
padding: 0;
|
168
|
+
}
|
169
|
+
|
170
|
+
body > * {
|
171
|
+
display: none !important;
|
172
|
+
}
|
173
|
+
|
174
|
+
head:after, body:before, body:after {
|
175
|
+
display: block !important;
|
176
|
+
}
|
177
|
+
|
178
|
+
head:after {
|
179
|
+
font-family: sans-serif;
|
180
|
+
font-size: large;
|
181
|
+
font-weight: bold;
|
182
|
+
content: "Error compiling CSS asset";
|
183
|
+
}
|
184
|
+
|
185
|
+
body:before, body:after {
|
186
|
+
font-family: monospace;
|
187
|
+
white-space: pre-wrap;
|
188
|
+
}
|
189
|
+
|
190
|
+
body:before {
|
191
|
+
font-weight: bold;
|
192
|
+
content: "#{escape_css_content(message)}";
|
193
|
+
}
|
194
|
+
|
195
|
+
body:after {
|
196
|
+
content: "#{escape_css_content(backtrace)}";
|
197
|
+
}
|
198
|
+
CSS
|
199
|
+
|
200
|
+
[ 200, { "Content-Type" => "text/css;charset=utf-8", "Content-Length" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
|
201
|
+
end
|
202
|
+
|
203
|
+
# Escape special characters for use inside a CSS content("...") string
|
204
|
+
def escape_css_content(content)
|
205
|
+
content.
|
206
|
+
gsub('\\', '\\\\005c ').
|
207
|
+
gsub("\n", '\\\\000a ').
|
208
|
+
gsub('"', '\\\\0022 ').
|
209
|
+
gsub('/', '\\\\002f ')
|
210
|
+
end
|
211
|
+
|
212
|
+
# Compare the requests `HTTP_IF_MODIFIED_SINCE` against the
|
213
|
+
# assets mtime
|
214
|
+
def not_modified?(asset, env)
|
215
|
+
env["HTTP_IF_MODIFIED_SINCE"] == asset.mtime.httpdate
|
216
|
+
end
|
217
|
+
|
218
|
+
# Compare the requests `HTTP_IF_NONE_MATCH` against the assets digest
|
219
|
+
def etag_match?(asset, env)
|
220
|
+
env["HTTP_IF_NONE_MATCH"] == etag(asset)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Test if `?body=1` or `body=true` query param is set
|
224
|
+
def body_only?(env)
|
225
|
+
env["QUERY_STRING"].to_s =~ /body=(1|t)/
|
226
|
+
end
|
227
|
+
|
228
|
+
# Returns a 304 Not Modified response tuple
|
229
|
+
def not_modified_response(asset, env)
|
230
|
+
[ 304, {}, [] ]
|
231
|
+
end
|
232
|
+
|
233
|
+
# Returns a 200 OK response tuple
|
234
|
+
def ok_response(asset, env)
|
235
|
+
if body_only?(env)
|
236
|
+
[ 200, headers(env, asset, Rack::Utils.bytesize(asset.body)), [asset.body] ]
|
237
|
+
else
|
238
|
+
[ 200, headers(env, asset, asset.length), asset ]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def headers(env, asset, length)
|
243
|
+
Hash.new.tap do |headers|
|
244
|
+
# Set content type and length headers
|
245
|
+
headers["Content-Type"] = asset.content_type
|
246
|
+
headers["Content-Length"] = length.to_s
|
247
|
+
|
248
|
+
# Set caching headers
|
249
|
+
headers["Cache-Control"] = "public"
|
250
|
+
headers["Last-Modified"] = asset.mtime.httpdate
|
251
|
+
headers["ETag"] = etag(asset)
|
252
|
+
|
253
|
+
# If the request url contains a fingerprint, set a long
|
254
|
+
# expires on the response
|
255
|
+
if attributes_for(env["PATH_INFO"]).path_fingerprint
|
256
|
+
headers["Cache-Control"] << ", max-age=31536000"
|
257
|
+
|
258
|
+
# Otherwise set `must-revalidate` since the asset could be modified.
|
259
|
+
else
|
260
|
+
headers["Cache-Control"] << ", must-revalidate"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Helper to quote the assets digest for use as an ETag.
|
266
|
+
def etag(asset)
|
267
|
+
%("#{asset.digest}")
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,87 @@
|
|
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
|
+
def initialize(environment, logical_path, pathname, digest = nil)
|
11
|
+
super(environment, logical_path, pathname)
|
12
|
+
@digest = digest
|
13
|
+
load!
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns file contents as its `body`.
|
17
|
+
def body
|
18
|
+
# File is read everytime to avoid memory bloat of large binary files
|
19
|
+
pathname.open('rb') { |f| f.read }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Checks if Asset is fresh by comparing the actual mtime and
|
23
|
+
# digest to the inmemory model.
|
24
|
+
def fresh?
|
25
|
+
# Check if environment has changed first
|
26
|
+
if environment.digest.hexdigest != environment_hexdigest
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
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
|
+
environment_hexdigest
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'sprockets/static_asset'
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module Sprockets
|
7
|
+
# `Caching` is an internal mixin whose public methods are exposed on
|
8
|
+
# the `Environment` and `Index` classes.
|
9
|
+
module StaticCompilation
|
10
|
+
# `static_root` is a special path where compiled assets are served
|
11
|
+
# from. This is usually set to a `/public` or `/static` directory.
|
12
|
+
#
|
13
|
+
# In a production environment, Apache or nginx should be
|
14
|
+
# configured to serve assets from the directory.
|
15
|
+
def static_root
|
16
|
+
@static_root
|
17
|
+
end
|
18
|
+
|
19
|
+
# Assign a static root directory.
|
20
|
+
def static_root=(root)
|
21
|
+
expire_index!
|
22
|
+
@static_root = root ? Pathname.new(root) : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# `precompile` takes a like of paths, globs, or `Regexp`s to
|
26
|
+
# compile into `static_root`.
|
27
|
+
#
|
28
|
+
# precompile "application.js", "*.css", /.+\.(png|jpg)/
|
29
|
+
#
|
30
|
+
# This usually ran via a rake task.
|
31
|
+
def precompile(*paths)
|
32
|
+
raise "missing static root" unless static_root
|
33
|
+
|
34
|
+
paths.each do |path|
|
35
|
+
files.each do |logical_path|
|
36
|
+
if path.is_a?(Regexp)
|
37
|
+
# Match path against `Regexp`
|
38
|
+
next unless path.match(logical_path.to_s)
|
39
|
+
else
|
40
|
+
# Otherwise use fnmatch glob syntax
|
41
|
+
next unless logical_path.fnmatch(path.to_s)
|
42
|
+
end
|
43
|
+
|
44
|
+
if asset = find_asset(logical_path)
|
45
|
+
attributes = attributes_for(logical_path)
|
46
|
+
digest_path = attributes.path_with_fingerprint(asset.digest)
|
47
|
+
filename = static_root.join(digest_path)
|
48
|
+
|
49
|
+
# Ensure directory exists
|
50
|
+
FileUtils.mkdir_p filename.dirname
|
51
|
+
|
52
|
+
# Write file
|
53
|
+
asset.write_to(filename)
|
54
|
+
|
55
|
+
# Write compressed file if its a bundled asset like .js or .css
|
56
|
+
asset.write_to("#{filename}.gz") if asset.is_a?(BundledAsset)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
def compute_digest
|
64
|
+
# Add static root to environment digest
|
65
|
+
super.update(static_root.to_s)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
# Get all reachable files in environment path
|
70
|
+
def files
|
71
|
+
files = Set.new
|
72
|
+
paths.each do |base_path|
|
73
|
+
base_pathname = Pathname.new(base_path)
|
74
|
+
Dir["#{base_pathname}/**/*"].each do |filename|
|
75
|
+
logical_path = Pathname.new(filename).relative_path_from(base_pathname)
|
76
|
+
files << attributes_for(logical_path).without_engine_extensions
|
77
|
+
end
|
78
|
+
end
|
79
|
+
files
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,122 @@
|
|
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.paths.unshift(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.paths.push(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.clear
|
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
|
+
trail.find(logical_path.to_s, attributes_for(logical_path).index_path, options) do |path|
|
74
|
+
yield Pathname.new(path)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
resolve(logical_path, options) do |pathname|
|
78
|
+
return pathname
|
79
|
+
end
|
80
|
+
raise FileNotFound, "couldn't find file '#{logical_path}'"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
def trail
|
86
|
+
@trail
|
87
|
+
end
|
88
|
+
|
89
|
+
def compute_digest
|
90
|
+
digest = super
|
91
|
+
|
92
|
+
# Add paths to environment digest.
|
93
|
+
digest << trail.paths.map { |p| attributes_for(p).relativize_root }.join(',')
|
94
|
+
|
95
|
+
digest
|
96
|
+
end
|
97
|
+
|
98
|
+
def find_asset_in_path(logical_path, options = {})
|
99
|
+
# Strip fingerprint on logical path if there is one.
|
100
|
+
# Not sure how valuable this feature is...
|
101
|
+
if fingerprint = attributes_for(logical_path).path_fingerprint
|
102
|
+
pathname = resolve(logical_path.to_s.sub("-#{fingerprint}", ''))
|
103
|
+
else
|
104
|
+
pathname = resolve(logical_path)
|
105
|
+
end
|
106
|
+
rescue FileNotFound
|
107
|
+
nil
|
108
|
+
else
|
109
|
+
# Build the asset for the actual pathname
|
110
|
+
asset = build_asset(logical_path, pathname, options)
|
111
|
+
|
112
|
+
# Double check request fingerprint against actual digest
|
113
|
+
# Again, not sure if this code path is even reachable
|
114
|
+
if fingerprint && fingerprint != asset.digest
|
115
|
+
logger.error "Nonexistent asset #{logical_path} @ #{fingerprint}"
|
116
|
+
asset = nil
|
117
|
+
end
|
118
|
+
|
119
|
+
asset
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Sprockets
|
2
|
+
# `Utils`, we didn't know where else to put it!
|
3
|
+
module Utils
|
4
|
+
# If theres encoding support (aka Ruby 1.9)
|
5
|
+
if "".respond_to?(:valid_encoding?)
|
6
|
+
# Define UTF-8 BOM pattern matcher.
|
7
|
+
# Avoid using a Regexp literal because it inheirts the files
|
8
|
+
# encoding and we want to avoid syntax errors in other interpreters.
|
9
|
+
UTF8_BOM_PATTERN = Regexp.new("\\A\uFEFF".encode('utf-8'))
|
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"
|
20
|
+
|
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, "")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
else
|
29
|
+
# Define UTF-8 and UTF-16 BOM pattern matchers.
|
30
|
+
# Avoid using a Regexp literal to prevent syntax errors in other interpreters.
|
31
|
+
UTF8_BOM_PATTERN = Regexp.new("\\A\\xEF\\xBB\\xBF")
|
32
|
+
UTF16_BOM_PATTERN = Regexp.new("\\A(\\xFE\\xFF|\\xFF\\xFE)")
|
33
|
+
|
34
|
+
def self.read_unicode(pathname)
|
35
|
+
pathname.read.tap do |data|
|
36
|
+
# If the file is UTF-8 and theres a BOM, strip it for safe concatenation.
|
37
|
+
if data =~ UTF8_BOM_PATTERN
|
38
|
+
data.sub!(UTF8_BOM_PATTERN, "")
|
39
|
+
|
40
|
+
# If we find a UTF-16 BOM, theres nothing we can do on
|
41
|
+
# 1.8. Only UTF-8 is supported.
|
42
|
+
elsif data =~ UTF16_BOM_PATTERN
|
43
|
+
raise EncodingError, "#{pathname} has a UTF-16 BOM. " +
|
44
|
+
"Resave the file as UTF-8 or upgrade to Ruby 1.9."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Prepends a leading "." to an extension if its missing.
|
51
|
+
#
|
52
|
+
# normalize_extension("js")
|
53
|
+
# # => ".js"
|
54
|
+
#
|
55
|
+
# normalize_extension(".css")
|
56
|
+
# # => ".css"
|
57
|
+
#
|
58
|
+
def self.normalize_extension(extension)
|
59
|
+
extension = extension.to_s
|
60
|
+
if extension[/^\./]
|
61
|
+
extension
|
62
|
+
else
|
63
|
+
".#{extension}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|