bpm 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (213) hide show
  1. data/.gitmodules +3 -0
  2. data/Gemfile +0 -13
  3. data/TODO.md +1 -0
  4. data/bpm.gemspec +8 -3
  5. data/lib/bpm/cli/base.rb +22 -20
  6. data/lib/bpm/generator.rb +3 -3
  7. data/lib/bpm/libgems_ext/dependency_installer.rb +4 -4
  8. data/lib/bpm/libgems_ext/installer.rb +1 -1
  9. data/lib/bpm/package.rb +13 -9
  10. data/lib/bpm/pipeline/generated_asset.rb +30 -3
  11. data/lib/bpm/pipeline/transport_processor.rb +20 -12
  12. data/lib/bpm/pipeline.rb +17 -9
  13. data/lib/bpm/project.rb +42 -10
  14. data/lib/bpm/server.rb +5 -0
  15. data/lib/bpm/version.rb +1 -1
  16. data/lib/bpm.rb +2 -0
  17. data/lib/vendored_sprockets.rb +7 -0
  18. data/spec/cli/add_spec.rb +40 -37
  19. data/spec/cli/build_spec.rb +5 -5
  20. data/spec/cli/new_spec.rb +49 -23
  21. data/spec/cli/push_spec.rb +5 -5
  22. data/spec/cli/unpack_spec.rb +14 -14
  23. data/spec/fixtures/{badrake-0.8.7.spd → badrake-0.8.7.bpkg} +0 -0
  24. data/spec/fixtures/{builder-3.0.0.spd → builder-3.0.0.bpkg} +0 -0
  25. data/spec/fixtures/{bundler-1.1.pre.spd → bundler-1.1.pre.bpkg} +0 -0
  26. data/spec/fixtures/{coffee-1.0.1.pre.spd → coffee-1.0.1.pre.bpkg} +0 -0
  27. data/spec/fixtures/{core-test-0.4.9.spd → core-test-0.4.9.bpkg} +0 -0
  28. data/spec/fixtures/{custom_generator-1.0.spd → custom_generator-1.0.bpkg} +0 -0
  29. data/spec/fixtures/custom_name/MyProject.json +4 -0
  30. data/spec/fixtures/hello_world/css/dummy.css +3 -0
  31. data/spec/fixtures/{highline-1.6.1.spd → highline-1.6.1.bpkg} +0 -0
  32. data/spec/fixtures/{ivory-0.0.1.spd → ivory-0.0.1.bpkg} +0 -0
  33. data/spec/fixtures/{jquery-1.4.3.spd → jquery-1.4.3.bpkg} +0 -0
  34. data/spec/fixtures/minitest/assets/bpm_packages.js +1 -0
  35. data/spec/fixtures/minitest/assets/bpm_styles.css +0 -0
  36. data/spec/fixtures/minitest/assets/minitest/app_package.js +1 -0
  37. data/spec/fixtures/minitest/lib/main.js +1 -0
  38. data/spec/fixtures/minitest/minitest.json +22 -0
  39. data/spec/fixtures/minitest/packages/uglyduck/lib/main.js +1 -0
  40. data/spec/fixtures/minitest/packages/uglyduck/minifier/main.js +4 -0
  41. data/spec/fixtures/minitest/packages/uglyduck/package.json +21 -0
  42. data/spec/fixtures/{optparse-1.0.1.spd → optparse-1.0.1.bpkg} +0 -0
  43. data/spec/fixtures/{rake-0.8.6.spd → rake-0.8.6.bpkg} +0 -0
  44. data/spec/fixtures/{rake-0.8.7.spd → rake-0.8.7.bpkg} +0 -0
  45. data/spec/fixtures/{spade-0.5.0.spd → spade-0.5.0.bpkg} +0 -0
  46. data/spec/fixtures/transporter/packages/transport/lib/main.js +1 -0
  47. data/spec/fixtures/transporter/packages/transport/package.json +1 -1
  48. data/spec/fixtures/transporter/packages/transport/transports/wrapper.js +5 -0
  49. data/spec/gauntlet_spec.rb +2 -2
  50. data/spec/package_spec.rb +3 -3
  51. data/spec/pipeline_spec.rb +175 -54
  52. data/spec/project_spec.rb +19 -3
  53. data/spec/support/fake_gem_server.rb +4 -4
  54. data/templates/init/project.json +3 -1
  55. data/templates/project/index.html +1 -1
  56. data/vendor/sprockets/.gitignore +7 -0
  57. data/vendor/sprockets/.travis.yml +6 -0
  58. data/vendor/sprockets/Gemfile +8 -0
  59. data/vendor/sprockets/LICENSE +20 -0
  60. data/vendor/sprockets/README.md +22 -0
  61. data/vendor/sprockets/Rakefile +8 -0
  62. data/vendor/sprockets/lib/sprockets/asset.rb +203 -0
  63. data/vendor/sprockets/lib/sprockets/asset_attributes.rb +161 -0
  64. data/vendor/sprockets/lib/sprockets/base.rb +147 -0
  65. data/vendor/sprockets/lib/sprockets/bundled_asset.rb +222 -0
  66. data/vendor/sprockets/lib/sprockets/cache/file_store.rb +41 -0
  67. data/vendor/sprockets/lib/sprockets/caching.rb +121 -0
  68. data/vendor/sprockets/lib/sprockets/charset_normalizer.rb +41 -0
  69. data/vendor/sprockets/lib/sprockets/context.rb +191 -0
  70. data/vendor/sprockets/lib/sprockets/digest.rb +73 -0
  71. data/vendor/sprockets/lib/sprockets/directive_processor.rb +380 -0
  72. data/vendor/sprockets/lib/sprockets/eco_template.rb +39 -0
  73. data/vendor/sprockets/lib/sprockets/ejs_template.rb +38 -0
  74. data/vendor/sprockets/lib/sprockets/engines.rb +92 -0
  75. data/vendor/sprockets/lib/sprockets/environment.rb +93 -0
  76. data/vendor/sprockets/lib/sprockets/errors.rb +17 -0
  77. data/vendor/sprockets/lib/sprockets/index.rb +80 -0
  78. data/vendor/sprockets/lib/sprockets/jst_processor.rb +26 -0
  79. data/vendor/sprockets/lib/sprockets/processing.rb +310 -0
  80. data/vendor/sprockets/lib/sprockets/processor.rb +32 -0
  81. data/vendor/sprockets/lib/sprockets/safety_colons.rb +28 -0
  82. data/vendor/sprockets/lib/sprockets/server.rb +270 -0
  83. data/vendor/sprockets/lib/sprockets/static_asset.rb +87 -0
  84. data/vendor/sprockets/lib/sprockets/static_compilation.rb +82 -0
  85. data/vendor/sprockets/lib/sprockets/trail.rb +122 -0
  86. data/vendor/sprockets/lib/sprockets/utils.rb +67 -0
  87. data/vendor/sprockets/lib/sprockets/version.rb +3 -0
  88. data/vendor/sprockets/lib/sprockets.rb +31 -0
  89. data/vendor/sprockets/sprockets.gemspec +30 -0
  90. data/vendor/sprockets/test/fixtures/asset/POW.png +0 -0
  91. data/vendor/sprockets/test/fixtures/asset/application.js +6 -0
  92. data/vendor/sprockets/test/fixtures/asset/bar-utf8.css +2 -0
  93. data/vendor/sprockets/test/fixtures/asset/charset.css +2 -0
  94. data/vendor/sprockets/test/fixtures/asset/circle/a.js +2 -0
  95. data/vendor/sprockets/test/fixtures/asset/circle/b.js +2 -0
  96. data/vendor/sprockets/test/fixtures/asset/circle/c.js +2 -0
  97. data/vendor/sprockets/test/fixtures/asset/compat.js +4 -0
  98. data/vendor/sprockets/test/fixtures/asset/constants.js +2 -0
  99. data/vendor/sprockets/test/fixtures/asset/constants.yml +1 -0
  100. data/vendor/sprockets/test/fixtures/asset/default_mime_type.js +1 -0
  101. data/vendor/sprockets/test/fixtures/asset/filename.js.erb +1 -0
  102. data/vendor/sprockets/test/fixtures/asset/foo-utf8.css +2 -0
  103. data/vendor/sprockets/test/fixtures/asset/included_header.js +4 -0
  104. data/vendor/sprockets/test/fixtures/asset/jquery.tmpl.min.js +1 -0
  105. data/vendor/sprockets/test/fixtures/asset/mismatch.js +1 -0
  106. data/vendor/sprockets/test/fixtures/asset/multiple.js +2 -0
  107. data/vendor/sprockets/test/fixtures/asset/multipleengine.js +1 -0
  108. data/vendor/sprockets/test/fixtures/asset/noengine.js +1 -0
  109. data/vendor/sprockets/test/fixtures/asset/noformat.coffee +1 -0
  110. data/vendor/sprockets/test/fixtures/asset/one.css +1 -0
  111. data/vendor/sprockets/test/fixtures/asset/oneengine.js +1 -0
  112. data/vendor/sprockets/test/fixtures/asset/project.css +1 -0
  113. data/vendor/sprockets/test/fixtures/asset/project.js.erb +4 -0
  114. data/vendor/sprockets/test/fixtures/asset/relative/include.js +4 -0
  115. data/vendor/sprockets/test/fixtures/asset/relative/require.js +1 -0
  116. data/vendor/sprockets/test/fixtures/asset/relative/require_outside_path.js +1 -0
  117. data/vendor/sprockets/test/fixtures/asset/require_self.css +9 -0
  118. data/vendor/sprockets/test/fixtures/asset/require_self_twice.css +8 -0
  119. data/vendor/sprockets/test/fixtures/asset/semicolons/bar.js +1 -0
  120. data/vendor/sprockets/test/fixtures/asset/semicolons/index.js +5 -0
  121. data/vendor/sprockets/test/fixtures/asset/sprite.css.erb +12 -0
  122. data/vendor/sprockets/test/fixtures/asset/tree/all/b/c/d.js +2 -0
  123. data/vendor/sprockets/test/fixtures/asset/tree/all/b/c/e.js +1 -0
  124. data/vendor/sprockets/test/fixtures/asset/tree/all/b/c.js +1 -0
  125. data/vendor/sprockets/test/fixtures/asset/tree/all/b.css +2 -0
  126. data/vendor/sprockets/test/fixtures/asset/tree/all/b.js.erb +1 -0
  127. data/vendor/sprockets/test/fixtures/asset/tree/all/d/c.js.coffee +1 -0
  128. data/vendor/sprockets/test/fixtures/asset/tree/all/d/e.js +1 -0
  129. data/vendor/sprockets/test/fixtures/asset/tree/all_with_require.js +7 -0
  130. data/vendor/sprockets/test/fixtures/asset/tree/all_with_require_directory.js +1 -0
  131. data/vendor/sprockets/test/fixtures/asset/tree/all_with_require_tree.js +2 -0
  132. data/vendor/sprockets/test/fixtures/asset/tree/directory/application.js +2 -0
  133. data/vendor/sprockets/test/fixtures/asset/tree/directory/bar.js +1 -0
  134. data/vendor/sprockets/test/fixtures/asset/tree/directory/foo.js +1 -0
  135. data/vendor/sprockets/test/fixtures/asset/tree/tree/application.js +2 -0
  136. data/vendor/sprockets/test/fixtures/asset/tree/tree/bar.js +1 -0
  137. data/vendor/sprockets/test/fixtures/asset/tree/tree/foo.js +1 -0
  138. data/vendor/sprockets/test/fixtures/asset/tree/with_logical_path/a/a.js +1 -0
  139. data/vendor/sprockets/test/fixtures/asset/tree/with_logical_path/require_tree_with_logical_path.js +1 -0
  140. data/vendor/sprockets/test/fixtures/asset/tree/without_argument/a.js +1 -0
  141. data/vendor/sprockets/test/fixtures/asset/tree/without_argument/b.js +1 -0
  142. data/vendor/sprockets/test/fixtures/asset/tree/without_argument/require_tree_without_argument.js +1 -0
  143. data/vendor/sprockets/test/fixtures/asset/two.css +1 -0
  144. data/vendor/sprockets/test/fixtures/asset/unicode.js +2 -0
  145. data/vendor/sprockets/test/fixtures/asset/unknownexts.min.js +2 -0
  146. data/vendor/sprockets/test/fixtures/asset/users.js.erb.str +4 -0
  147. data/vendor/sprockets/test/fixtures/context/POW.png +0 -0
  148. data/vendor/sprockets/test/fixtures/context/application.js.yml +3 -0
  149. data/vendor/sprockets/test/fixtures/context/bar.js +1 -0
  150. data/vendor/sprockets/test/fixtures/context/foo.js +2 -0
  151. data/vendor/sprockets/test/fixtures/context/helpers.css.erb +3 -0
  152. data/vendor/sprockets/test/fixtures/context/properties.js.erb +7 -0
  153. data/vendor/sprockets/test/fixtures/context/require_glob.js +1 -0
  154. data/vendor/sprockets/test/fixtures/context/resolve_content_type.js.erb +4 -0
  155. data/vendor/sprockets/test/fixtures/context/sprite.css.embed +3 -0
  156. data/vendor/sprockets/test/fixtures/default/application.js.coffee +4 -0
  157. data/vendor/sprockets/test/fixtures/default/coffee/foo.coffee +1 -0
  158. data/vendor/sprockets/test/fixtures/default/coffee/index.js +3 -0
  159. data/vendor/sprockets/test/fixtures/default/empty +0 -0
  160. data/vendor/sprockets/test/fixtures/default/gallery.css.erb +3 -0
  161. data/vendor/sprockets/test/fixtures/default/gallery.js +1 -0
  162. data/vendor/sprockets/test/fixtures/default/goodbye.jst.eco +1 -0
  163. data/vendor/sprockets/test/fixtures/default/hello.jst.ejs +1 -0
  164. data/vendor/sprockets/test/fixtures/default/hello.txt +1 -0
  165. data/vendor/sprockets/test/fixtures/default/interpolation.js +1 -0
  166. data/vendor/sprockets/test/fixtures/default/missing_require.js +1 -0
  167. data/vendor/sprockets/test/fixtures/default/mobile/a.js +1 -0
  168. data/vendor/sprockets/test/fixtures/default/mobile/b.js +1 -0
  169. data/vendor/sprockets/test/fixtures/default/mobile/c.css +1 -0
  170. data/vendor/sprockets/test/fixtures/default/mobile/d.css +1 -0
  171. data/vendor/sprockets/test/fixtures/default/mobile/index.css +3 -0
  172. data/vendor/sprockets/test/fixtures/default/mobile/index.js +1 -0
  173. data/vendor/sprockets/test/fixtures/default/noreturn.js +1 -0
  174. data/vendor/sprockets/test/fixtures/default/project.js.coffee.erb +2 -0
  175. data/vendor/sprockets/test/fixtures/directives/code_before_comment +3 -0
  176. data/vendor/sprockets/test/fixtures/directives/comment_without_directives +6 -0
  177. data/vendor/sprockets/test/fixtures/directives/directive_word_splitting +6 -0
  178. data/vendor/sprockets/test/fixtures/directives/directives_after_header +16 -0
  179. data/vendor/sprockets/test/fixtures/directives/double_slash +9 -0
  180. data/vendor/sprockets/test/fixtures/directives/hash +8 -0
  181. data/vendor/sprockets/test/fixtures/directives/no_header +2 -0
  182. data/vendor/sprockets/test/fixtures/directives/slash_star +10 -0
  183. data/vendor/sprockets/test/fixtures/directives/slash_star_single +4 -0
  184. data/vendor/sprockets/test/fixtures/directives/space_between_directive_word +2 -0
  185. data/vendor/sprockets/test/fixtures/directives/triple_hash +10 -0
  186. data/vendor/sprockets/test/fixtures/encoding/ascii.js +1 -0
  187. data/vendor/sprockets/test/fixtures/encoding/ascii_utf8.js +2 -0
  188. data/vendor/sprockets/test/fixtures/encoding/utf16.js +0 -0
  189. data/vendor/sprockets/test/fixtures/encoding/utf8.js +1 -0
  190. data/vendor/sprockets/test/fixtures/encoding/utf8_bom.js +1 -0
  191. data/vendor/sprockets/test/fixtures/engines/hello.alert +1 -0
  192. data/vendor/sprockets/test/fixtures/engines/moo.js.str +1 -0
  193. data/vendor/sprockets/test/fixtures/public/compiled-digest-0aa2105d29558f3eb790d411d7d8fb66.js +3 -0
  194. data/vendor/sprockets/test/fixtures/public/compiled-digest-1c41eb0cf934a0c76babe875f982f9d1.js +1 -0
  195. data/vendor/sprockets/test/fixtures/server/app/javascripts/application.js +5 -0
  196. data/vendor/sprockets/test/fixtures/server/app/javascripts/bar.js +1 -0
  197. data/vendor/sprockets/test/fixtures/server/app/javascripts/foo.js +1 -0
  198. data/vendor/sprockets/test/fixtures/server/app/javascripts/hello.txt +2 -0
  199. data/vendor/sprockets/test/fixtures/server/app/javascripts/tree.js +1 -0
  200. data/vendor/sprockets/test/fixtures/server/vendor/javascripts/missing_require.js +1 -0
  201. data/vendor/sprockets/test/fixtures/server/vendor/stylesheets/missing_require.css +1 -0
  202. data/vendor/sprockets/test/sprockets_test.rb +56 -0
  203. data/vendor/sprockets/test/test_asset.rb +593 -0
  204. data/vendor/sprockets/test/test_asset_attributes.rb +86 -0
  205. data/vendor/sprockets/test/test_caching.rb +62 -0
  206. data/vendor/sprockets/test/test_context.rb +115 -0
  207. data/vendor/sprockets/test/test_directive_processor.rb +124 -0
  208. data/vendor/sprockets/test/test_encoding.rb +65 -0
  209. data/vendor/sprockets/test/test_engines.rb +73 -0
  210. data/vendor/sprockets/test/test_environment.rb +610 -0
  211. data/vendor/sprockets/test/test_server.rb +227 -0
  212. metadata +258 -54
  213. 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