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,32 @@
1
+ require 'tilt'
2
+
3
+ module Sprockets
4
+ # `Processor` creates an anonymous processor class from a block.
5
+ #
6
+ # register_preprocessor :my_processor do |context, data|
7
+ # # ...
8
+ # end
9
+ #
10
+ class Processor < Tilt::Template
11
+ # `processor` is a lambda or block
12
+ def self.processor
13
+ @processor
14
+ end
15
+
16
+ def self.name
17
+ "Sprockets::Processor (#{@name})"
18
+ end
19
+
20
+ def self.to_s
21
+ name
22
+ end
23
+
24
+ def prepare
25
+ end
26
+
27
+ # Call processor block with `context` and `data`.
28
+ def evaluate(context, locals)
29
+ self.class.processor.call(context, data)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ require 'tilt'
2
+
3
+ module Sprockets
4
+ # For JS developers who are colonfobic, concatenating JS files using
5
+ # the module pattern usually leads to syntax errors.
6
+ #
7
+ # The `SafetyColons` processor will insert missing semicolons to the
8
+ # end of the file.
9
+ #
10
+ # This behavior can be disabled with:
11
+ #
12
+ # environment.unregister_postprocessor 'application/javascript', Sprockets::SafetyColons
13
+ #
14
+ class SafetyColons < Tilt::Template
15
+ def prepare
16
+ end
17
+
18
+ def evaluate(context, locals, &block)
19
+ # If the file is blank or ends in a semicolon, leave it as is
20
+ if data =~ /\A\s*\Z/m || data =~ /;\s*\Z/m
21
+ data
22
+ else
23
+ # Otherwise, append a semicolon and newline
24
+ "#{data};\n"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,270 @@
1
+ require 'rack/request'
2
+ require 'time'
3
+
4
+ module Sprockets
5
+ # `Server` is a concern mixed into `Environment` and
6
+ # `Index` that provides a Rack compatible `call`
7
+ # interface and url generation helpers.
8
+ module Server
9
+ # `call` implements the Rack 1.x specification which accepts an
10
+ # `env` Hash and returns a three item tuple with the status code,
11
+ # headers, and body.
12
+ #
13
+ # Mapping your environment at a url prefix will serve all assets
14
+ # in the path.
15
+ #
16
+ # map "/assets" do
17
+ # run Sprockets::Environment.new
18
+ # end
19
+ #
20
+ # A request for `"/assets/foo/bar.js"` will search your
21
+ # environment for `"foo/bar.js"`.
22
+ def call(env)
23
+ start_time = Time.now.to_f
24
+ time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
25
+
26
+ msg = "Served asset #{env['PATH_INFO']} -"
27
+
28
+ # URLs containing a `".."` are rejected for security reasons.
29
+ if forbidden_request?(env)
30
+ return forbidden_response
31
+ end
32
+
33
+ # Mark session as "skipped" so no `Set-Cookie` header is set
34
+ env['rack.session.options'] ||= {}
35
+ env['rack.session.options'][:defer] = true
36
+ env['rack.session.options'][:skip] = true
37
+
38
+ # Extract the path from everything after the leading slash
39
+ path = env['PATH_INFO'].to_s.sub(/^\//, '')
40
+
41
+ # Look up the asset.
42
+ asset = find_asset(path)
43
+ asset.to_a if asset
44
+
45
+ # `find_asset` returns nil if the asset doesn't exist
46
+ if asset.nil?
47
+ logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
48
+
49
+ # Return a 404 Not Found
50
+ not_found_response
51
+
52
+ # Check request headers `HTTP_IF_MODIFIED_SINCE` and
53
+ # `HTTP_IF_NONE_MATCH` against the assets mtime and digest
54
+ elsif not_modified?(asset, env) || etag_match?(asset, env)
55
+ logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)"
56
+
57
+ # Return a 304 Not Modified
58
+ not_modified_response(asset, env)
59
+
60
+ else
61
+ logger.info "#{msg} 200 OK (#{time_elapsed.call}ms)"
62
+
63
+ # Return a 200 with the asset contents
64
+ ok_response(asset, env)
65
+ end
66
+ rescue Exception => e
67
+ logger.error "Error compiling asset #{path}:"
68
+ logger.error "#{e.class.name}: #{e.message}"
69
+
70
+ case content_type_of(path)
71
+ when "application/javascript"
72
+ # Re-throw JavaScript asset exceptions to the browser
73
+ logger.info "#{msg} 500 Internal Server Error\n\n"
74
+ return javascript_exception_response(e)
75
+ when "text/css"
76
+ # Display CSS asset exceptions in the browser
77
+ logger.info "#{msg} 500 Internal Server Error\n\n"
78
+ return css_exception_response(e)
79
+ else
80
+ raise
81
+ end
82
+ end
83
+
84
+ # `path` is a url helper that looks up an asset given a
85
+ # `logical_path` and returns a path String. By default, the
86
+ # asset's digest fingerprint is spliced into the filename.
87
+ #
88
+ # /assets/application-3676d55f84497cbeadfc614c1b1b62fc.js
89
+ #
90
+ # A third `prefix` argument can be pass along to be prepended to
91
+ # the string.
92
+ def path(logical_path, fingerprint = true, prefix = nil)
93
+ if fingerprint && asset = find_asset(logical_path.to_s.sub(/^\//, ''))
94
+ url = attributes_for(logical_path).path_with_fingerprint(asset.digest)
95
+ else
96
+ url = logical_path
97
+ end
98
+
99
+ url = File.join(prefix, url) if prefix
100
+ url = "/#{url}" unless url =~ /^\//
101
+
102
+ url
103
+ end
104
+
105
+ # Similar to `path`, `url` returns a full url given a Rack `env`
106
+ # Hash and a `logical_path`.
107
+ def url(env, logical_path, fingerprint = true, prefix = nil)
108
+ req = Rack::Request.new(env)
109
+
110
+ url = req.scheme + "://"
111
+ url << req.host
112
+
113
+ if req.scheme == "https" && req.port != 443 ||
114
+ req.scheme == "http" && req.port != 80
115
+ url << ":#{req.port}"
116
+ end
117
+
118
+ url << path(logical_path, fingerprint, prefix)
119
+
120
+ url
121
+ end
122
+
123
+ private
124
+ def forbidden_request?(env)
125
+ # Prevent access to files elsewhere on the file system
126
+ #
127
+ # http://example.org/assets/../../../etc/passwd
128
+ #
129
+ env["PATH_INFO"].include?("..")
130
+ end
131
+
132
+ # Returns a 403 Forbidden response tuple
133
+ def forbidden_response
134
+ [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
135
+ end
136
+
137
+ # Returns a 404 Not Found response tuple
138
+ def not_found_response
139
+ [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
140
+ end
141
+
142
+ # Returns a JavaScript response that re-throws a Ruby exception
143
+ # in the browser
144
+ def javascript_exception_response(exception)
145
+ err = "#{exception.class.name}: #{exception.message}"
146
+ body = "throw Error(#{err.inspect})"
147
+ [ 200, { "Content-Type" => "application/javascript", "Content-Length" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
148
+ end
149
+
150
+ # Returns a CSS response that hides all elements on the page and
151
+ # displays the exception
152
+ def css_exception_response(exception)
153
+ message = "\n#{exception.class.name}: #{exception.message}"
154
+ backtrace = "\n #{exception.backtrace.first}"
155
+
156
+ body = <<-CSS
157
+ html {
158
+ padding: 18px 36px;
159
+ }
160
+
161
+ head {
162
+ display: block;
163
+ }
164
+
165
+ body {
166
+ margin: 0;
167
+ padding: 0;
168
+ }
169
+
170
+ body > * {
171
+ display: none !important;
172
+ }
173
+
174
+ head:after, body:before, body:after {
175
+ display: block !important;
176
+ }
177
+
178
+ head:after {
179
+ font-family: sans-serif;
180
+ font-size: large;
181
+ font-weight: bold;
182
+ content: "Error compiling CSS asset";
183
+ }
184
+
185
+ body:before, body:after {
186
+ font-family: monospace;
187
+ white-space: pre-wrap;
188
+ }
189
+
190
+ body:before {
191
+ font-weight: bold;
192
+ content: "#{escape_css_content(message)}";
193
+ }
194
+
195
+ body:after {
196
+ content: "#{escape_css_content(backtrace)}";
197
+ }
198
+ CSS
199
+
200
+ [ 200, { "Content-Type" => "text/css;charset=utf-8", "Content-Length" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
201
+ end
202
+
203
+ # Escape special characters for use inside a CSS content("...") string
204
+ def escape_css_content(content)
205
+ content.
206
+ gsub('\\', '\\\\005c ').
207
+ gsub("\n", '\\\\000a ').
208
+ gsub('"', '\\\\0022 ').
209
+ gsub('/', '\\\\002f ')
210
+ end
211
+
212
+ # Compare the requests `HTTP_IF_MODIFIED_SINCE` against the
213
+ # assets mtime
214
+ def not_modified?(asset, env)
215
+ env["HTTP_IF_MODIFIED_SINCE"] == asset.mtime.httpdate
216
+ end
217
+
218
+ # Compare the requests `HTTP_IF_NONE_MATCH` against the assets digest
219
+ def etag_match?(asset, env)
220
+ env["HTTP_IF_NONE_MATCH"] == etag(asset)
221
+ end
222
+
223
+ # Test if `?body=1` or `body=true` query param is set
224
+ def body_only?(env)
225
+ env["QUERY_STRING"].to_s =~ /body=(1|t)/
226
+ end
227
+
228
+ # Returns a 304 Not Modified response tuple
229
+ def not_modified_response(asset, env)
230
+ [ 304, {}, [] ]
231
+ end
232
+
233
+ # Returns a 200 OK response tuple
234
+ def ok_response(asset, env)
235
+ if body_only?(env)
236
+ [ 200, headers(env, asset, Rack::Utils.bytesize(asset.body)), [asset.body] ]
237
+ else
238
+ [ 200, headers(env, asset, asset.length), asset ]
239
+ end
240
+ end
241
+
242
+ def headers(env, asset, length)
243
+ Hash.new.tap do |headers|
244
+ # Set content type and length headers
245
+ headers["Content-Type"] = asset.content_type
246
+ headers["Content-Length"] = length.to_s
247
+
248
+ # Set caching headers
249
+ headers["Cache-Control"] = "public"
250
+ headers["Last-Modified"] = asset.mtime.httpdate
251
+ headers["ETag"] = etag(asset)
252
+
253
+ # If the request url contains a fingerprint, set a long
254
+ # expires on the response
255
+ if attributes_for(env["PATH_INFO"]).path_fingerprint
256
+ headers["Cache-Control"] << ", max-age=31536000"
257
+
258
+ # Otherwise set `must-revalidate` since the asset could be modified.
259
+ else
260
+ headers["Cache-Control"] << ", must-revalidate"
261
+ end
262
+ end
263
+ end
264
+
265
+ # Helper to quote the assets digest for use as an ETag.
266
+ def etag(asset)
267
+ %("#{asset.digest}")
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,87 @@
1
+ require 'sprockets/asset'
2
+ require 'fileutils'
3
+ require 'zlib'
4
+
5
+ module Sprockets
6
+ # `StaticAsset`s are used for files that are served verbatim without
7
+ # any processing or concatenation. These are typical images and
8
+ # other binary files.
9
+ class StaticAsset < Asset
10
+ def initialize(environment, logical_path, pathname, digest = nil)
11
+ super(environment, logical_path, pathname)
12
+ @digest = digest
13
+ load!
14
+ end
15
+
16
+ # Returns file contents as its `body`.
17
+ def body
18
+ # File is read everytime to avoid memory bloat of large binary files
19
+ pathname.open('rb') { |f| f.read }
20
+ end
21
+
22
+ # Checks if Asset is fresh by comparing the actual mtime and
23
+ # digest to the inmemory model.
24
+ def fresh?
25
+ # Check if environment has changed first
26
+ if environment.digest.hexdigest != environment_hexdigest
27
+ return false
28
+ end
29
+
30
+ # Check current mtime and digest
31
+ dependency_fresh?('path' => pathname, 'mtime' => mtime, 'hexdigest' => digest)
32
+ end
33
+
34
+ # Implemented for Rack SendFile support.
35
+ def to_path
36
+ pathname.to_s
37
+ end
38
+
39
+ # `to_s` is aliased to body since static assets can't have any dependencies.
40
+ def to_s
41
+ body
42
+ end
43
+
44
+ # Save asset to disk.
45
+ def write_to(filename, options = {})
46
+ # Gzip contents if filename has '.gz'
47
+ options[:compress] ||= File.extname(filename) == '.gz'
48
+
49
+ if options[:compress]
50
+ # Open file and run it through `Zlib`
51
+ pathname.open('rb') do |rd|
52
+ File.open("#{filename}+", 'wb') do |wr|
53
+ gz = Zlib::GzipWriter.new(wr, Zlib::BEST_COMPRESSION)
54
+ buf = ""
55
+ while rd.read(16384, buf)
56
+ gz.write(buf)
57
+ end
58
+ gz.close
59
+ end
60
+ end
61
+ else
62
+ # If no compression needs to be done, we can just copy it into place.
63
+ FileUtils.cp(pathname, "#{filename}+")
64
+ end
65
+
66
+ # Atomic write
67
+ FileUtils.mv("#{filename}+", filename)
68
+
69
+ # Set mtime correctly
70
+ File.utime(mtime, mtime, filename)
71
+
72
+ nil
73
+ ensure
74
+ # Ensure tmp file gets cleaned up
75
+ FileUtils.rm("#{filename}+") if File.exist?("#{filename}+")
76
+ end
77
+
78
+ private
79
+ def load!
80
+ content_type
81
+ mtime
82
+ length
83
+ digest
84
+ environment_hexdigest
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,82 @@
1
+ require 'sprockets/static_asset'
2
+
3
+ require 'fileutils'
4
+ require 'pathname'
5
+
6
+ module Sprockets
7
+ # `Caching` is an internal mixin whose public methods are exposed on
8
+ # the `Environment` and `Index` classes.
9
+ module StaticCompilation
10
+ # `static_root` is a special path where compiled assets are served
11
+ # from. This is usually set to a `/public` or `/static` directory.
12
+ #
13
+ # In a production environment, Apache or nginx should be
14
+ # configured to serve assets from the directory.
15
+ def static_root
16
+ @static_root
17
+ end
18
+
19
+ # Assign a static root directory.
20
+ def static_root=(root)
21
+ expire_index!
22
+ @static_root = root ? Pathname.new(root) : nil
23
+ end
24
+
25
+ # `precompile` takes a like of paths, globs, or `Regexp`s to
26
+ # compile into `static_root`.
27
+ #
28
+ # precompile "application.js", "*.css", /.+\.(png|jpg)/
29
+ #
30
+ # This usually ran via a rake task.
31
+ def precompile(*paths)
32
+ raise "missing static root" unless static_root
33
+
34
+ paths.each do |path|
35
+ files.each do |logical_path|
36
+ if path.is_a?(Regexp)
37
+ # Match path against `Regexp`
38
+ next unless path.match(logical_path.to_s)
39
+ else
40
+ # Otherwise use fnmatch glob syntax
41
+ next unless logical_path.fnmatch(path.to_s)
42
+ end
43
+
44
+ if asset = find_asset(logical_path)
45
+ attributes = attributes_for(logical_path)
46
+ digest_path = attributes.path_with_fingerprint(asset.digest)
47
+ filename = static_root.join(digest_path)
48
+
49
+ # Ensure directory exists
50
+ FileUtils.mkdir_p filename.dirname
51
+
52
+ # Write file
53
+ asset.write_to(filename)
54
+
55
+ # Write compressed file if its a bundled asset like .js or .css
56
+ asset.write_to("#{filename}.gz") if asset.is_a?(BundledAsset)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ protected
63
+ def compute_digest
64
+ # Add static root to environment digest
65
+ super.update(static_root.to_s)
66
+ end
67
+
68
+ private
69
+ # Get all reachable files in environment path
70
+ def files
71
+ files = Set.new
72
+ paths.each do |base_path|
73
+ base_pathname = Pathname.new(base_path)
74
+ Dir["#{base_pathname}/**/*"].each do |filename|
75
+ logical_path = Pathname.new(filename).relative_path_from(base_pathname)
76
+ files << attributes_for(logical_path).without_engine_extensions
77
+ end
78
+ end
79
+ files
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,122 @@
1
+ require 'sprockets/errors'
2
+ require 'pathname'
3
+
4
+ module Sprockets
5
+ # `Trail` is an internal mixin whose public methods are exposed on
6
+ # the `Environment` and `Index` classes.
7
+ module Trail
8
+ # Returns `Environment` root.
9
+ #
10
+ # All relative paths are expanded with root as its base. To be
11
+ # useful set this to your applications root directory. (`Rails.root`)
12
+ def root
13
+ trail.root.dup
14
+ end
15
+
16
+ # Returns an `Array` of path `String`s.
17
+ #
18
+ # These paths will be used for asset logical path lookups.
19
+ #
20
+ # Note that a copy of the `Array` is returned so mutating will
21
+ # have no affect on the environment. See `append_path`,
22
+ # `prepend_path`, and `clear_paths`.
23
+ def paths
24
+ trail.paths.dup
25
+ end
26
+
27
+ # Prepend a `path` to the `paths` list.
28
+ #
29
+ # Paths at the end of the `Array` have the least priority.
30
+ def prepend_path(path)
31
+ expire_index!
32
+ @trail.paths.unshift(path)
33
+ end
34
+
35
+ # Append a `path` to the `paths` list.
36
+ #
37
+ # Paths at the beginning of the `Array` have a higher priority.
38
+ def append_path(path)
39
+ expire_index!
40
+ @trail.paths.push(path)
41
+ end
42
+
43
+ # Clear all paths and start fresh.
44
+ #
45
+ # There is no mechanism for reordering paths, so its best to
46
+ # completely wipe the paths list and reappend them in the order
47
+ # you want.
48
+ def clear_paths
49
+ expire_index!
50
+ @trail.paths.clear
51
+ end
52
+
53
+ # Returns an `Array` of extensions.
54
+ #
55
+ # These extensions maybe omitted from logical path searches.
56
+ #
57
+ # # => [".js", ".css", ".coffee", ".sass", ...]
58
+ #
59
+ def extensions
60
+ trail.extensions.dup
61
+ end
62
+
63
+ # Finds the expanded real path for a given logical path by
64
+ # searching the environment's paths.
65
+ #
66
+ # resolve("application.js")
67
+ # # => "/path/to/app/javascripts/application.js.coffee"
68
+ #
69
+ # A `FileNotFound` exception is raised if the file does not exist.
70
+ def resolve(logical_path, options = {})
71
+ # If a block is given, preform an iterable search
72
+ if block_given?
73
+ trail.find(logical_path.to_s, attributes_for(logical_path).index_path, options) do |path|
74
+ yield Pathname.new(path)
75
+ end
76
+ else
77
+ resolve(logical_path, options) do |pathname|
78
+ return pathname
79
+ end
80
+ raise FileNotFound, "couldn't find file '#{logical_path}'"
81
+ end
82
+ end
83
+
84
+ protected
85
+ def trail
86
+ @trail
87
+ end
88
+
89
+ def compute_digest
90
+ digest = super
91
+
92
+ # Add paths to environment digest.
93
+ digest << trail.paths.map { |p| attributes_for(p).relativize_root }.join(',')
94
+
95
+ digest
96
+ end
97
+
98
+ def find_asset_in_path(logical_path, options = {})
99
+ # Strip fingerprint on logical path if there is one.
100
+ # Not sure how valuable this feature is...
101
+ if fingerprint = attributes_for(logical_path).path_fingerprint
102
+ pathname = resolve(logical_path.to_s.sub("-#{fingerprint}", ''))
103
+ else
104
+ pathname = resolve(logical_path)
105
+ end
106
+ rescue FileNotFound
107
+ nil
108
+ else
109
+ # Build the asset for the actual pathname
110
+ asset = build_asset(logical_path, pathname, options)
111
+
112
+ # Double check request fingerprint against actual digest
113
+ # Again, not sure if this code path is even reachable
114
+ if fingerprint && fingerprint != asset.digest
115
+ logger.error "Nonexistent asset #{logical_path} @ #{fingerprint}"
116
+ asset = nil
117
+ end
118
+
119
+ asset
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,67 @@
1
+ module Sprockets
2
+ # `Utils`, we didn't know where else to put it!
3
+ module Utils
4
+ # If theres encoding support (aka Ruby 1.9)
5
+ if "".respond_to?(:valid_encoding?)
6
+ # Define UTF-8 BOM pattern matcher.
7
+ # Avoid using a Regexp literal because it inheirts the files
8
+ # encoding and we want to avoid syntax errors in other interpreters.
9
+ UTF8_BOM_PATTERN = Regexp.new("\\A\uFEFF".encode('utf-8'))
10
+
11
+ def self.read_unicode(pathname)
12
+ pathname.read.tap do |data|
13
+ # Eager validate the file's encoding. In most cases we
14
+ # expect it to be UTF-8 unless `default_external` is set to
15
+ # something else. An error is usually raised if the file is
16
+ # saved as UTF-16 when we expected UTF-8.
17
+ if !data.valid_encoding?
18
+ raise EncodingError, "#{pathname} has a invalid " +
19
+ "#{data.encoding} byte sequence"
20
+
21
+ # If the file is UTF-8 and theres a BOM, strip it for safe concatenation.
22
+ elsif data.encoding.name == "UTF-8" && data =~ UTF8_BOM_PATTERN
23
+ data.sub!(UTF8_BOM_PATTERN, "")
24
+ end
25
+ end
26
+ end
27
+
28
+ else
29
+ # Define UTF-8 and UTF-16 BOM pattern matchers.
30
+ # Avoid using a Regexp literal to prevent syntax errors in other interpreters.
31
+ UTF8_BOM_PATTERN = Regexp.new("\\A\\xEF\\xBB\\xBF")
32
+ UTF16_BOM_PATTERN = Regexp.new("\\A(\\xFE\\xFF|\\xFF\\xFE)")
33
+
34
+ def self.read_unicode(pathname)
35
+ pathname.read.tap do |data|
36
+ # If the file is UTF-8 and theres a BOM, strip it for safe concatenation.
37
+ if data =~ UTF8_BOM_PATTERN
38
+ data.sub!(UTF8_BOM_PATTERN, "")
39
+
40
+ # If we find a UTF-16 BOM, theres nothing we can do on
41
+ # 1.8. Only UTF-8 is supported.
42
+ elsif data =~ UTF16_BOM_PATTERN
43
+ raise EncodingError, "#{pathname} has a UTF-16 BOM. " +
44
+ "Resave the file as UTF-8 or upgrade to Ruby 1.9."
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Prepends a leading "." to an extension if its missing.
51
+ #
52
+ # normalize_extension("js")
53
+ # # => ".js"
54
+ #
55
+ # normalize_extension(".css")
56
+ # # => ".css"
57
+ #
58
+ def self.normalize_extension(extension)
59
+ extension = extension.to_s
60
+ if extension[/^\./]
61
+ extension
62
+ else
63
+ ".#{extension}"
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module Sprockets
2
+ VERSION = "2.0.0.beta.11"
3
+ end