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,161 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# `AssetAttributes` is a wrapper similar to `Pathname` that provides
|
5
|
+
# some helper accessors.
|
6
|
+
#
|
7
|
+
# These methods should be considered internalish.
|
8
|
+
class AssetAttributes
|
9
|
+
attr_reader :environment, :pathname
|
10
|
+
|
11
|
+
def initialize(environment, path)
|
12
|
+
@environment = environment
|
13
|
+
@pathname = path.is_a?(Pathname) ? path : Pathname.new(path.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns `Array` of extension `String`s.
|
17
|
+
#
|
18
|
+
# "foo.js.coffee"
|
19
|
+
# # => [".js", ".coffee"]
|
20
|
+
#
|
21
|
+
def extensions
|
22
|
+
@extensions ||= @pathname.basename.to_s.scan(/\.[^.]+/)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns basename alone.
|
26
|
+
#
|
27
|
+
# "foo/bar.js"
|
28
|
+
# # => "bar"
|
29
|
+
#
|
30
|
+
def basename_without_extensions
|
31
|
+
@pathname.basename(extensions.join)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Replaces `$root` placeholder with actual environment root.
|
35
|
+
def expand_root
|
36
|
+
pathname.to_s.sub(/^\$root/, environment.root)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Replaces environment root with `$root` placeholder.
|
40
|
+
def relativize_root
|
41
|
+
pathname.to_s.sub(/^#{Regexp.escape(environment.root)}/, '$root')
|
42
|
+
end
|
43
|
+
|
44
|
+
# Strips `$HOME` and environment root for a nicer output.
|
45
|
+
def pretty_path
|
46
|
+
@pretty_path ||= @pathname.
|
47
|
+
sub(/^#{Regexp.escape(ENV['HOME'] || '')}/, '~').
|
48
|
+
sub(/^#{Regexp.escape(environment.root)}\//, '')
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the index location.
|
52
|
+
#
|
53
|
+
# "foo/bar.js"
|
54
|
+
# # => "foo/bar/index.js"
|
55
|
+
#
|
56
|
+
def index_path
|
57
|
+
if basename_without_extensions.to_s == 'index'
|
58
|
+
pathname.to_s
|
59
|
+
else
|
60
|
+
basename = "#{basename_without_extensions}/index#{extensions.join}"
|
61
|
+
pathname.dirname.to_s == '.' ? basename : pathname.dirname.join(basename).to_s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the format extension.
|
66
|
+
#
|
67
|
+
# "foo.js.coffee"
|
68
|
+
# # => ".js"
|
69
|
+
#
|
70
|
+
def format_extension
|
71
|
+
extensions.detect { |ext| @environment.mime_types(ext) }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns an `Array` of engine extensions.
|
75
|
+
#
|
76
|
+
# "foo.js.coffee.erb"
|
77
|
+
# # => [".coffee", ".erb"]
|
78
|
+
#
|
79
|
+
def engine_extensions
|
80
|
+
exts = extensions
|
81
|
+
|
82
|
+
if offset = extensions.index(format_extension)
|
83
|
+
exts = extensions[offset+1..-1]
|
84
|
+
end
|
85
|
+
|
86
|
+
exts.select { |ext| @environment.engines(ext) }
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns path without any engine extensions.
|
90
|
+
#
|
91
|
+
# "foo.js.coffee.erb"
|
92
|
+
# # => "foo.js"
|
93
|
+
#
|
94
|
+
def without_engine_extensions
|
95
|
+
engine_extensions.inject(pathname) do |p, ext|
|
96
|
+
p.sub(ext, '')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns engine classes.
|
101
|
+
def engines
|
102
|
+
engine_extensions.map { |ext| @environment.engines(ext) }
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns all processors to run on the path.
|
106
|
+
def processors
|
107
|
+
environment.preprocessors(content_type) +
|
108
|
+
engines.reverse +
|
109
|
+
environment.postprocessors(content_type)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns implicit engine content type.
|
113
|
+
#
|
114
|
+
# `.coffee` files carry an implicit `application/javascript`
|
115
|
+
# content type.
|
116
|
+
def engine_content_type
|
117
|
+
engines.reverse.each do |engine|
|
118
|
+
if engine.respond_to?(:default_mime_type) && engine.default_mime_type
|
119
|
+
return engine.default_mime_type
|
120
|
+
end
|
121
|
+
end
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns the content type for the pathname. Falls back to `application/octet-stream`.
|
126
|
+
def content_type
|
127
|
+
@content_type ||= begin
|
128
|
+
if format_extension.nil?
|
129
|
+
engine_content_type || 'application/octet-stream'
|
130
|
+
else
|
131
|
+
@environment.mime_types(format_extension) ||
|
132
|
+
engine_content_type ||
|
133
|
+
'application/octet-stream'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Gets digest fingerprint.
|
139
|
+
#
|
140
|
+
# "foo-0aa2105d29558f3eb790d411d7d8fb66.js"
|
141
|
+
# # => "0aa2105d29558f3eb790d411d7d8fb66"
|
142
|
+
#
|
143
|
+
def path_fingerprint
|
144
|
+
pathname.basename(extensions.join).to_s =~ /-([0-9a-f]{7,40})$/ ? $1 : nil
|
145
|
+
end
|
146
|
+
|
147
|
+
# Injects digest fingerprint into path.
|
148
|
+
#
|
149
|
+
# "foo.js"
|
150
|
+
# # => "foo-0aa2105d29558f3eb790d411d7d8fb66.js"
|
151
|
+
#
|
152
|
+
def path_with_fingerprint(digest)
|
153
|
+
if path_fingerprint
|
154
|
+
path.sub($1, digest)
|
155
|
+
else
|
156
|
+
basename = "#{pathname.basename(extensions.join)}-#{digest}#{extensions.join}"
|
157
|
+
pathname.dirname.to_s == '.' ? basename : pathname.dirname.join(basename).to_s
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'sprockets/asset_attributes'
|
2
|
+
require 'sprockets/bundled_asset'
|
3
|
+
require 'sprockets/caching'
|
4
|
+
require 'sprockets/digest'
|
5
|
+
require 'sprockets/processing'
|
6
|
+
require 'sprockets/server'
|
7
|
+
require 'sprockets/static_asset'
|
8
|
+
require 'sprockets/static_compilation'
|
9
|
+
require 'sprockets/trail'
|
10
|
+
require 'pathname'
|
11
|
+
|
12
|
+
module Sprockets
|
13
|
+
# `Base` class for `Environment` and `Index`.
|
14
|
+
class Base
|
15
|
+
include Digest
|
16
|
+
include Caching, Processing, Server, StaticCompilation, Trail
|
17
|
+
|
18
|
+
# Get and set `Logger` instance.
|
19
|
+
attr_accessor :logger
|
20
|
+
|
21
|
+
# Get `Context` class.
|
22
|
+
#
|
23
|
+
# This class maybe mutated and mixed in with custom helpers.
|
24
|
+
#
|
25
|
+
# environment.context_class.instance_eval do
|
26
|
+
# include MyHelpers
|
27
|
+
# def asset_url; end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
attr_reader :context_class
|
31
|
+
|
32
|
+
# Get persistent cache store
|
33
|
+
attr_reader :cache
|
34
|
+
|
35
|
+
# Set persistent cache store
|
36
|
+
#
|
37
|
+
# The cache store must implement a pair of getters and
|
38
|
+
# setters. Either `get(key)`/`set(key, value)`,
|
39
|
+
# `[key]`/`[key]=value`, `read(key)`/`write(key, value)`.
|
40
|
+
def cache=(cache)
|
41
|
+
expire_index!
|
42
|
+
@cache = cache
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return an `Index`. Must be implemented by the subclass.
|
46
|
+
def index
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
|
50
|
+
# Works like `Dir.entries`.
|
51
|
+
#
|
52
|
+
# Subclasses may cache this method.
|
53
|
+
def entries(pathname)
|
54
|
+
trail.entries(pathname)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Works like `File.stat`.
|
58
|
+
#
|
59
|
+
# Subclasses may cache this method.
|
60
|
+
def stat(path)
|
61
|
+
trail.stat(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Read and compute digest of filename.
|
65
|
+
#
|
66
|
+
# Subclasses may cache this method.
|
67
|
+
def file_digest(path, data = nil)
|
68
|
+
if stat = self.stat(path)
|
69
|
+
# `data` maybe provided
|
70
|
+
if data
|
71
|
+
digest.update(data)
|
72
|
+
|
73
|
+
# If its a file, digest the contents
|
74
|
+
elsif stat.file?
|
75
|
+
digest.file(path)
|
76
|
+
|
77
|
+
# If its a directive, digest the list of filenames
|
78
|
+
elsif stat.directory?
|
79
|
+
contents = self.entries(path).join(',')
|
80
|
+
digest.update(contents)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Internal. Return a `AssetAttributes` for `path`.
|
86
|
+
def attributes_for(path)
|
87
|
+
AssetAttributes.new(self, path)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Internal. Return content type of `path`.
|
91
|
+
def content_type_of(path)
|
92
|
+
attributes_for(path).content_type
|
93
|
+
end
|
94
|
+
|
95
|
+
# Find asset by logical path or expanded path.
|
96
|
+
def find_asset(path, options = {})
|
97
|
+
pathname = Pathname.new(path)
|
98
|
+
|
99
|
+
if pathname.absolute?
|
100
|
+
build_asset(detect_logical_path(path).to_s, pathname, options)
|
101
|
+
else
|
102
|
+
find_asset_in_path(pathname, options)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Preferred `find_asset` shorthand.
|
107
|
+
#
|
108
|
+
# environment['application.js']
|
109
|
+
#
|
110
|
+
def [](*args)
|
111
|
+
find_asset(*args)
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
# Clear index after mutating state. Must be implemented by the subclass.
|
116
|
+
def expire_index!
|
117
|
+
raise NotImplementedError
|
118
|
+
end
|
119
|
+
|
120
|
+
def build_asset(logical_path, pathname, options)
|
121
|
+
pathname = Pathname.new(pathname)
|
122
|
+
|
123
|
+
return unless stat(pathname)
|
124
|
+
|
125
|
+
# If there are any processors to run on the pathname, use
|
126
|
+
# `BundledAsset`. Otherwise use `StaticAsset` and treat is as binary.
|
127
|
+
if attributes_for(pathname).processors.any?
|
128
|
+
BundledAsset.new(self, logical_path, pathname, options)
|
129
|
+
else
|
130
|
+
StaticAsset.new(self, logical_path, pathname)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Reverse guess logical path for fully expanded path.#
|
135
|
+
#
|
136
|
+
# This has some known issues. For an example if a file is
|
137
|
+
# shaddowed in the path, but is required relatively, its logical
|
138
|
+
# path will be incorrect.
|
139
|
+
def detect_logical_path(filename)
|
140
|
+
if root_path = paths.detect { |path| filename.to_s[path] }
|
141
|
+
root_pathname = Pathname.new(root_path)
|
142
|
+
logical_path = Pathname.new(filename).relative_path_from(root_pathname)
|
143
|
+
attributes_for(logical_path).without_engine_extensions
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'sprockets/asset'
|
2
|
+
require 'sprockets/errors'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'set'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
module Sprockets
|
8
|
+
# `BundledAsset`s are used for files that need to be processed and
|
9
|
+
# concatenated with other assets. Use for `.js` and `.css` files.
|
10
|
+
class BundledAsset < Asset
|
11
|
+
def initialize(environment, logical_path, pathname, options)
|
12
|
+
super(environment, logical_path, pathname)
|
13
|
+
@options = options || {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Initialize `BundledAsset` from serialized `Hash`.
|
17
|
+
def init_with(environment, coder)
|
18
|
+
super
|
19
|
+
|
20
|
+
@options = {}
|
21
|
+
|
22
|
+
@body = coder['body']
|
23
|
+
@source = coder['source']
|
24
|
+
@assets = coder['asset_paths'].map { |p|
|
25
|
+
p = expand_root_path(p)
|
26
|
+
p == pathname.to_s ? self : environment[p, @options]
|
27
|
+
}
|
28
|
+
|
29
|
+
@dependency_files = coder['dependency_files'].map { |h|
|
30
|
+
h.merge('path' => expand_root_path(h['path']))
|
31
|
+
}
|
32
|
+
@dependency_files.each do |dep|
|
33
|
+
dep['mtime'] = Time.parse(dep['mtime']) if dep['mtime'].is_a?(String)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Serialize custom attributes in `BundledAsset`.
|
38
|
+
def encode_with(coder)
|
39
|
+
super
|
40
|
+
|
41
|
+
coder['body'] = body
|
42
|
+
coder['source'] = to_s
|
43
|
+
coder['asset_paths'] = to_a.map { |a| relativize_root_path(a.pathname) }
|
44
|
+
coder['dependency_files'] = dependency_files.map { |h|
|
45
|
+
h.merge('path' => relativize_root_path(h['path']))
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get asset's own processed contents. Excludes any of its required
|
50
|
+
# dependencies but does run any processors or engines on the
|
51
|
+
# original file.
|
52
|
+
def body
|
53
|
+
@body ||= dependency_context_and_body[1]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get latest mtime of all its dependencies.
|
57
|
+
def mtime
|
58
|
+
@mtime ||= dependency_files.map { |h| h['mtime'] }.max
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get size of concatenated source.
|
62
|
+
def length
|
63
|
+
@length ||= Rack::Utils.bytesize(to_s)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Compute digest of concatenated source.
|
67
|
+
def digest
|
68
|
+
@digest ||= environment.digest.update(to_s).hexdigest
|
69
|
+
end
|
70
|
+
|
71
|
+
# Return an `Array` of `Asset` files that are declared dependencies.
|
72
|
+
def dependencies
|
73
|
+
to_a - [self]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Expand asset into an `Array` of parts.
|
77
|
+
def to_a
|
78
|
+
@assets ||= compute_assets
|
79
|
+
end
|
80
|
+
|
81
|
+
# Checks if Asset is stale by comparing the actual mtime and
|
82
|
+
# digest to the inmemory model.
|
83
|
+
def fresh?
|
84
|
+
# Check if environment has changed first
|
85
|
+
if environment.digest.hexdigest != environment_hexdigest
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
|
89
|
+
# Check freshness of all declared dependencies
|
90
|
+
dependency_files.all? { |h| dependency_fresh?(h) }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Return `String` of concatenated source.
|
94
|
+
def to_s
|
95
|
+
@source ||= build_source
|
96
|
+
end
|
97
|
+
|
98
|
+
# Save asset to disk.
|
99
|
+
def write_to(filename, options = {})
|
100
|
+
# Gzip contents if filename has '.gz'
|
101
|
+
options[:compress] ||= File.extname(filename) == '.gz'
|
102
|
+
|
103
|
+
File.open("#{filename}+", 'wb') do |f|
|
104
|
+
if options[:compress]
|
105
|
+
# Run contents through `Zlib`
|
106
|
+
gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
|
107
|
+
gz.write to_s
|
108
|
+
gz.close
|
109
|
+
else
|
110
|
+
# Write out as is
|
111
|
+
f.write to_s
|
112
|
+
f.close
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Atomic write
|
117
|
+
FileUtils.mv("#{filename}+", filename)
|
118
|
+
|
119
|
+
# Set mtime correctly
|
120
|
+
File.utime(mtime, mtime, filename)
|
121
|
+
|
122
|
+
nil
|
123
|
+
ensure
|
124
|
+
# Ensure tmp file gets cleaned up
|
125
|
+
FileUtils.rm("#{filename}+") if File.exist?("#{filename}+")
|
126
|
+
end
|
127
|
+
|
128
|
+
protected
|
129
|
+
# Return new blank `Context` to evaluate processors in.
|
130
|
+
def blank_context
|
131
|
+
environment.context_class.new(environment, logical_path.to_s, pathname)
|
132
|
+
end
|
133
|
+
|
134
|
+
def dependency_context_and_body
|
135
|
+
@dependency_context_and_body ||= build_dependency_context_and_body
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get `Context` after processors have been ran on it. This
|
139
|
+
# trackes any dependencies that processors have added to it.
|
140
|
+
def dependency_context
|
141
|
+
dependency_context_and_body[0]
|
142
|
+
end
|
143
|
+
|
144
|
+
# All files that this asset depends on. This list may include
|
145
|
+
# non-assets like directories.
|
146
|
+
def dependency_files
|
147
|
+
@dependency_files ||= dependency_context._dependency_paths.to_a.map do |path|
|
148
|
+
{ 'path' => path,
|
149
|
+
'mtime' => environment.stat(path).mtime,
|
150
|
+
'hexdigest' => environment.file_digest(path).hexdigest }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
# Check if self has already been required and raise a fast
|
156
|
+
# error. Otherwise you end up with a StackOverflow error.
|
157
|
+
def check_circular_dependency!
|
158
|
+
requires = @options[:_requires] ||= []
|
159
|
+
if requires.include?(pathname.to_s)
|
160
|
+
raise CircularDependencyError, "#{pathname} has already been required"
|
161
|
+
end
|
162
|
+
requires << pathname.to_s
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_dependency_context_and_body
|
166
|
+
context = blank_context
|
167
|
+
|
168
|
+
# Read original data once and pass it along to `Context`
|
169
|
+
data = Sprockets::Utils.read_unicode(pathname)
|
170
|
+
|
171
|
+
# Prime digest cache with data, since we happen to have it
|
172
|
+
environment.file_digest(pathname, data)
|
173
|
+
|
174
|
+
# Runs all processors on `Context`
|
175
|
+
body = context.evaluate(pathname, :data => data)
|
176
|
+
|
177
|
+
return context, body
|
178
|
+
end
|
179
|
+
|
180
|
+
def build_source
|
181
|
+
data = ""
|
182
|
+
|
183
|
+
# Explode Asset into parts and gather the dependency bodies
|
184
|
+
to_a.each { |dependency| data << dependency.body }
|
185
|
+
|
186
|
+
# Run bundle processors on concatenated source
|
187
|
+
blank_context.evaluate(pathname, :data => data,
|
188
|
+
:processors => environment.bundle_processors(content_type))
|
189
|
+
end
|
190
|
+
|
191
|
+
def compute_assets
|
192
|
+
check_circular_dependency!
|
193
|
+
|
194
|
+
assets = []
|
195
|
+
|
196
|
+
# Define an `add_dependency` helper
|
197
|
+
add_dependency = lambda do |asset|
|
198
|
+
unless assets.any? { |a| a.pathname == asset.pathname }
|
199
|
+
assets << asset
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Iterate over all the declared require paths from the `Context`
|
204
|
+
dependency_context._required_paths.each do |required_path|
|
205
|
+
# Catch `require_self`
|
206
|
+
if required_path == pathname.to_s
|
207
|
+
add_dependency.call(self)
|
208
|
+
else
|
209
|
+
# Recursively lookup required asset
|
210
|
+
environment[required_path, @options].to_a.each do |asset|
|
211
|
+
add_dependency.call(asset)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Ensure self is added to the dependency list
|
217
|
+
add_dependency.call(self)
|
218
|
+
|
219
|
+
assets
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Sprockets
|
6
|
+
module Cache
|
7
|
+
# A simple file system cache store.
|
8
|
+
#
|
9
|
+
# environment.cache = Sprockets::Cache::FileStore.new("tmp/sprockets")
|
10
|
+
#
|
11
|
+
class FileStore
|
12
|
+
def initialize(root)
|
13
|
+
@root = Pathname.new(root)
|
14
|
+
|
15
|
+
# Ensure directory exists
|
16
|
+
FileUtils.mkdir_p @root
|
17
|
+
end
|
18
|
+
|
19
|
+
# Lookup value in cache
|
20
|
+
def [](key)
|
21
|
+
pathname = path_for(key)
|
22
|
+
pathname.exist? ? pathname.open('rb') { |f| Marshal.load(f) } : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Save value to cache
|
26
|
+
def []=(key, value)
|
27
|
+
path_for(key).open('w') { |f| Marshal.dump(value, f)}
|
28
|
+
value
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
# Returns path for cache key.
|
33
|
+
#
|
34
|
+
# The key may include some funky characters so hash it into
|
35
|
+
# safe hex.
|
36
|
+
def path_for(key)
|
37
|
+
@root.join(::Digest::MD5.hexdigest(key))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'sprockets/bundled_asset'
|
2
|
+
require 'sprockets/static_asset'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
# `Caching` is an internal mixin whose public methods are exposed on
|
6
|
+
# the `Environment` and `Index` classes.
|
7
|
+
module Caching
|
8
|
+
# Return `Asset` instance for serialized `Hash`.
|
9
|
+
def asset_from_hash(hash)
|
10
|
+
case hash['class']
|
11
|
+
when 'BundledAsset'
|
12
|
+
BundledAsset.from_hash(self, hash)
|
13
|
+
when 'StaticAsset'
|
14
|
+
StaticAsset.from_hash(self, hash)
|
15
|
+
else
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
# Cache helper method. Takes a `path` argument which maybe a
|
22
|
+
# logical path or fully expanded path. The `&block` is passed
|
23
|
+
# for finding and building the asset if its not in cache.
|
24
|
+
def cache_asset(path)
|
25
|
+
# If `cache` is not set, return fast
|
26
|
+
if cache.nil?
|
27
|
+
yield
|
28
|
+
|
29
|
+
# Check cache for `path`
|
30
|
+
elsif asset = cache_get_asset(path)
|
31
|
+
asset
|
32
|
+
|
33
|
+
# Otherwise yield block that slowly finds and builds the asset
|
34
|
+
elsif asset = yield
|
35
|
+
# Save the asset to at its path
|
36
|
+
cache_set_asset(path.to_s, asset)
|
37
|
+
|
38
|
+
# Since path maybe a logical or full pathname, save the
|
39
|
+
# asset its its full path too
|
40
|
+
if path.to_s != asset.pathname.to_s
|
41
|
+
cache_set_asset(asset.pathname.to_s, asset)
|
42
|
+
end
|
43
|
+
|
44
|
+
asset
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def cache_key_namespace
|
50
|
+
'sprockets'
|
51
|
+
end
|
52
|
+
|
53
|
+
# Removes `Environment#root` from key and prepends
|
54
|
+
# `Environment#cache_key_namespace`.
|
55
|
+
def cache_key_for(path)
|
56
|
+
File.join(cache_key_namespace, path.sub(root, ''))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Gets asset from cache and unserializes it
|
60
|
+
def cache_get_asset(path)
|
61
|
+
hash = cache_get(cache_key_for(path))
|
62
|
+
|
63
|
+
if hash.is_a?(Hash)
|
64
|
+
asset = asset_from_hash(hash)
|
65
|
+
|
66
|
+
if asset.fresh?
|
67
|
+
asset
|
68
|
+
end
|
69
|
+
else
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Serializes and saves asset to cache
|
75
|
+
def cache_set_asset(path, asset)
|
76
|
+
hash = {}
|
77
|
+
asset.encode_with(hash)
|
78
|
+
cache_set(cache_key_for(path), hash)
|
79
|
+
asset
|
80
|
+
end
|
81
|
+
|
82
|
+
# Low level cache getter for `key`. Checks a number of supported
|
83
|
+
# cache interfaces.
|
84
|
+
def cache_get(key)
|
85
|
+
# `Cache#get(key)` for Memcache
|
86
|
+
if cache.respond_to?(:get)
|
87
|
+
cache.get(key)
|
88
|
+
|
89
|
+
# `Cache#[key]` so `Hash` can be used
|
90
|
+
elsif cache.respond_to?(:[])
|
91
|
+
cache[key]
|
92
|
+
|
93
|
+
# `Cache#read(key)` for `ActiveSupport::Cache` support
|
94
|
+
elsif cache.respond_to?(:read)
|
95
|
+
cache.read(key)
|
96
|
+
|
97
|
+
else
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Low level cache setter for `key`. Checks a number of supported
|
103
|
+
# cache interfaces.
|
104
|
+
def cache_set(key, value)
|
105
|
+
# `Cache#set(key, value)` for Memcache
|
106
|
+
if cache.respond_to?(:set)
|
107
|
+
cache.set(key, value)
|
108
|
+
|
109
|
+
# `Cache#[key]=value` so `Hash` can be used
|
110
|
+
elsif cache.respond_to?(:[]=)
|
111
|
+
cache[key] = value
|
112
|
+
|
113
|
+
# `Cache#write(key, value)` for `ActiveSupport::Cache` support
|
114
|
+
elsif cache.respond_to?(:write)
|
115
|
+
cache.write(key, value)
|
116
|
+
end
|
117
|
+
|
118
|
+
value
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|