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