bpm 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|