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,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
|