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