sprockets 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sprockets might be problematic. Click here for more details.

Files changed (64) hide show
  1. data/LICENSE +21 -0
  2. data/README.md +356 -0
  3. data/lib/sprockets.rb +26 -37
  4. data/lib/sprockets/asset.rb +212 -0
  5. data/lib/sprockets/asset_attributes.rb +158 -0
  6. data/lib/sprockets/base.rb +163 -0
  7. data/lib/sprockets/bundled_asset.rb +258 -0
  8. data/lib/sprockets/cache/file_store.rb +41 -0
  9. data/lib/sprockets/caching.rb +123 -0
  10. data/lib/sprockets/charset_normalizer.rb +41 -0
  11. data/lib/sprockets/context.rb +217 -0
  12. data/lib/sprockets/digest.rb +67 -0
  13. data/lib/sprockets/directive_processor.rb +380 -0
  14. data/lib/sprockets/eco_template.rb +38 -0
  15. data/lib/sprockets/ejs_template.rb +37 -0
  16. data/lib/sprockets/engines.rb +98 -0
  17. data/lib/sprockets/environment.rb +81 -40
  18. data/lib/sprockets/errors.rb +17 -0
  19. data/lib/sprockets/index.rb +79 -0
  20. data/lib/sprockets/jst_processor.rb +26 -0
  21. data/lib/sprockets/mime.rb +38 -0
  22. data/lib/sprockets/processing.rb +280 -0
  23. data/lib/sprockets/processor.rb +32 -0
  24. data/lib/sprockets/safety_colons.rb +28 -0
  25. data/lib/sprockets/server.rb +272 -0
  26. data/lib/sprockets/static_asset.rb +86 -0
  27. data/lib/sprockets/trail.rb +114 -0
  28. data/lib/sprockets/utils.rb +67 -0
  29. data/lib/sprockets/version.rb +1 -7
  30. metadata +212 -64
  31. data/Rakefile +0 -19
  32. data/bin/sprocketize +0 -54
  33. data/ext/nph-sprockets.cgi +0 -127
  34. data/lib/sprockets/concatenation.rb +0 -36
  35. data/lib/sprockets/error.rb +0 -5
  36. data/lib/sprockets/pathname.rb +0 -37
  37. data/lib/sprockets/preprocessor.rb +0 -91
  38. data/lib/sprockets/secretary.rb +0 -106
  39. data/lib/sprockets/source_file.rb +0 -54
  40. data/lib/sprockets/source_line.rb +0 -82
  41. data/test/fixtures/assets/images/script_with_assets/one.png +0 -1
  42. data/test/fixtures/assets/images/script_with_assets/two.png +0 -1
  43. data/test/fixtures/assets/stylesheets/script_with_assets.css +0 -1
  44. data/test/fixtures/constants.yml +0 -1
  45. data/test/fixtures/double_slash_comments_that_are_not_requires_should_be_ignored_when_strip_comments_is_false.js +0 -8
  46. data/test/fixtures/double_slash_comments_that_are_not_requires_should_be_removed_by_default.js +0 -2
  47. data/test/fixtures/multiline_comments_should_be_removed_by_default.js +0 -4
  48. data/test/fixtures/requiring_a_file_after_it_has_already_been_required_should_do_nothing.js +0 -5
  49. data/test/fixtures/requiring_a_file_that_does_not_exist_should_raise_an_error.js +0 -1
  50. data/test/fixtures/requiring_a_single_file_should_replace_the_require_comment_with_the_file_contents.js +0 -3
  51. data/test/fixtures/requiring_the_current_file_should_do_nothing.js +0 -1
  52. data/test/fixtures/src/constants.yml +0 -3
  53. data/test/fixtures/src/foo.js +0 -1
  54. data/test/fixtures/src/foo/bar.js +0 -4
  55. data/test/fixtures/src/foo/foo.js +0 -1
  56. data/test/fixtures/src/script_with_assets.js +0 -3
  57. data/test/test_concatenation.rb +0 -28
  58. data/test/test_environment.rb +0 -64
  59. data/test/test_helper.rb +0 -55
  60. data/test/test_pathname.rb +0 -43
  61. data/test/test_preprocessor.rb +0 -107
  62. data/test/test_secretary.rb +0 -83
  63. data/test/test_source_file.rb +0 -34
  64. data/test/test_source_line.rb +0 -89
@@ -0,0 +1,380 @@
1
+ require 'pathname'
2
+ require 'shellwords'
3
+ require 'tilt'
4
+ require 'yaml'
5
+
6
+ module Sprockets
7
+ # The `DirectiveProcessor` is responsible for parsing and evaluating
8
+ # directive comments in a source file.
9
+ #
10
+ # A directive comment starts with a comment prefix, followed by an "=",
11
+ # then the directive name, then any arguments.
12
+ #
13
+ # // JavaScript
14
+ # //= require "foo"
15
+ #
16
+ # # CoffeeScript
17
+ # #= require "bar"
18
+ #
19
+ # /* CSS
20
+ # *= require "baz"
21
+ # */
22
+ #
23
+ # The Processor is implemented as a `Tilt::Template` and is loosely
24
+ # coupled to Sprockets. This makes it possible to disable or modify
25
+ # the processor to do whatever you'd like. You could add your own
26
+ # custom directives or invent your own directive syntax.
27
+ #
28
+ # `Environment#processors` includes `DirectiveProcessor` by default.
29
+ #
30
+ # To remove the processor entirely:
31
+ #
32
+ # env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
33
+ # env.unregister_processor('application/javascript', Sprockets::DirectiveProcessor)
34
+ #
35
+ # Then inject your own preprocessor:
36
+ #
37
+ # env.register_processor('text/css', MyProcessor)
38
+ #
39
+ class DirectiveProcessor < Tilt::Template
40
+ # Directives will only be picked up if they are in the header
41
+ # of the source file. C style (/* */), JavaScript (//), and
42
+ # Ruby (#) comments are supported.
43
+ #
44
+ # Directives in comments after the first non-whitespace line
45
+ # of code will not be processed.
46
+ #
47
+ HEADER_PATTERN = /
48
+ \A (
49
+ (?m:\s*) (
50
+ (\/\* (?m:.*?) \*\/) |
51
+ (\#\#\# (?m:.*?) \#\#\#) |
52
+ (\/\/ .* \n?)+ |
53
+ (\# .* \n?)+
54
+ )
55
+ )+
56
+ /x
57
+
58
+ # Directives are denoted by a `=` followed by the name, then
59
+ # argument list.
60
+ #
61
+ # A few different styles are allowed:
62
+ #
63
+ # // =require foo
64
+ # //= require foo
65
+ # //= require "foo"
66
+ #
67
+ DIRECTIVE_PATTERN = /
68
+ ^ [\W]* = \s* (\w+.*?) (\*\/)? $
69
+ /x
70
+
71
+ attr_reader :pathname
72
+ attr_reader :header, :body
73
+
74
+ def prepare
75
+ @pathname = Pathname.new(file)
76
+
77
+ @header = data[HEADER_PATTERN, 0] || ""
78
+ @body = $' || data
79
+ # Ensure body ends in a new line
80
+ @body += "\n" if @body != "" && @body !~ /\n\Z/m
81
+
82
+ @included_pathnames = []
83
+ @compat = false
84
+ end
85
+
86
+ # Implemented for Tilt#render.
87
+ #
88
+ # `context` is a `Context` instance with methods that allow you to
89
+ # access the environment and append to the bundle. See `Context`
90
+ # for the complete API.
91
+ def evaluate(context, locals, &block)
92
+ @context = context
93
+
94
+ @result = ""
95
+ @has_written_body = false
96
+
97
+ process_directives
98
+ process_source
99
+
100
+ @result
101
+ end
102
+
103
+ # Returns the header String with any directives stripped.
104
+ def processed_header
105
+ lineno = 0
106
+ @processed_header ||= header.lines.reject { |line|
107
+ lineno += 1
108
+ directives.assoc(lineno)
109
+ }.join.chomp
110
+ end
111
+
112
+ # Returns the source String with any directives stripped.
113
+ def processed_source
114
+ @processed_source ||= processed_header + body
115
+ end
116
+
117
+ # Returns an Array of directive structures. Each structure
118
+ # is an Array with the line number as the first element, the
119
+ # directive name as the second element, followed by any
120
+ # arguments.
121
+ #
122
+ # [[1, "require", "foo"], [2, "require", "bar"]]
123
+ #
124
+ def directives
125
+ @directives ||= header.lines.each_with_index.map { |line, index|
126
+ if directive = line[DIRECTIVE_PATTERN, 1]
127
+ name, *args = Shellwords.shellwords(directive)
128
+ if respond_to?("process_#{name}_directive")
129
+ [index + 1, name, *args]
130
+ end
131
+ end
132
+ }.compact
133
+ end
134
+
135
+ protected
136
+ attr_reader :included_pathnames
137
+ attr_reader :context
138
+
139
+ # Gathers comment directives in the source and processes them.
140
+ # Any directive method matching `process_*_directive` will
141
+ # automatically be available. This makes it easy to extend the
142
+ # processor.
143
+ #
144
+ # To implement a custom directive called `require_glob`, subclass
145
+ # `Sprockets::DirectiveProcessor`, then add a method called
146
+ # `process_require_glob_directive`.
147
+ #
148
+ # class DirectiveProcessor < Sprockets::DirectiveProcessor
149
+ # def process_require_glob_directive
150
+ # Dir["#{pathname.dirname}/#{glob}"].sort.each do |filename|
151
+ # require(filename)
152
+ # end
153
+ # end
154
+ # end
155
+ #
156
+ # Replace the current processor on the environment with your own:
157
+ #
158
+ # env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
159
+ # env.register_processor('text/css', DirectiveProcessor)
160
+ #
161
+ def process_directives
162
+ directives.each do |line_number, name, *args|
163
+ context.__LINE__ = line_number
164
+ send("process_#{name}_directive", *args)
165
+ context.__LINE__ = nil
166
+ end
167
+ end
168
+
169
+ def process_source
170
+ unless @has_written_body || processed_header.empty?
171
+ @result << processed_header << "\n"
172
+ end
173
+
174
+ included_pathnames.each do |pathname|
175
+ @result << context.evaluate(pathname)
176
+ end
177
+
178
+ unless @has_written_body
179
+ @result << body
180
+ end
181
+
182
+ if compat? && constants.any?
183
+ @result.gsub!(/<%=(.*?)%>/) { constants[$1.strip] }
184
+ end
185
+ end
186
+
187
+ # The `require` directive functions similar to Ruby's own `require`.
188
+ # It provides a way to declare a dependency on a file in your path
189
+ # and ensures its only loaded once before the source file.
190
+ #
191
+ # `require` works with files in the environment path:
192
+ #
193
+ # //= require "foo.js"
194
+ #
195
+ # Extensions are optional. If your source file is ".js", it
196
+ # assumes you are requiring another ".js".
197
+ #
198
+ # //= require "foo"
199
+ #
200
+ # Relative paths work too. Use a leading `./` to denote a relative
201
+ # path:
202
+ #
203
+ # //= require "./bar"
204
+ #
205
+ def process_require_directive(path)
206
+ if @compat
207
+ if path =~ /<([^>]+)>/
208
+ path = $1
209
+ else
210
+ path = "./#{path}" unless relative?(path)
211
+ end
212
+ end
213
+
214
+ context.require_asset(path)
215
+ end
216
+
217
+ # `require_self` causes the body of the current file to be
218
+ # inserted before any subsequent `require` or `include`
219
+ # directives. Useful in CSS files, where it's common for the
220
+ # index file to contain global styles that need to be defined
221
+ # before other dependencies are loaded.
222
+ #
223
+ # /*= require "reset"
224
+ # *= require_self
225
+ # *= require_tree .
226
+ # */
227
+ #
228
+ def process_require_self_directive
229
+ if @has_written_body
230
+ raise ArgumentError, "require_self can only be called once per source file"
231
+ end
232
+
233
+ context.require_asset(pathname)
234
+ process_source
235
+ included_pathnames.clear
236
+ @has_written_body = true
237
+ end
238
+
239
+ # The `include` directive works similar to `require` but
240
+ # inserts the contents of the dependency even if it already
241
+ # has been required.
242
+ #
243
+ # //= include "header"
244
+ #
245
+ def process_include_directive(path)
246
+ pathname = context.resolve(path)
247
+ context.depend_on_asset(pathname)
248
+ included_pathnames << pathname
249
+ end
250
+
251
+ # `require_directory` requires all the files inside a single
252
+ # directory. It's similar to `path/*` since it does not follow
253
+ # nested directories.
254
+ #
255
+ # //= require_directory "./javascripts"
256
+ #
257
+ def process_require_directory_directive(path = ".")
258
+ if relative?(path)
259
+ root = pathname.dirname.join(path).expand_path
260
+
261
+ unless root.directory?
262
+ raise ArgumentError, "require_tree argument must be a directory"
263
+ end
264
+
265
+ context.depend_on(root)
266
+
267
+ Dir["#{root}/*"].sort.each do |filename|
268
+ if filename == self.file
269
+ next
270
+ elsif context.asset_requirable?(filename)
271
+ context.require_asset(filename)
272
+ end
273
+ end
274
+ else
275
+ # The path must be relative and start with a `./`.
276
+ raise ArgumentError, "require_directory argument must be a relative path"
277
+ end
278
+ end
279
+
280
+ # `require_tree` requires all the nested files in a directory.
281
+ # Its glob equivalent is `path/**/*`.
282
+ #
283
+ # //= require_tree "./public"
284
+ #
285
+ def process_require_tree_directive(path = ".")
286
+ if relative?(path)
287
+ root = pathname.dirname.join(path).expand_path
288
+
289
+ unless root.directory?
290
+ raise ArgumentError, "require_tree argument must be a directory"
291
+ end
292
+
293
+ context.depend_on(root)
294
+
295
+ Dir["#{root}/**/*"].sort.each do |filename|
296
+ if filename == self.file
297
+ next
298
+ elsif File.directory?(filename)
299
+ context.depend_on(filename)
300
+ elsif context.asset_requirable?(filename)
301
+ context.require_asset(filename)
302
+ end
303
+ end
304
+ else
305
+ # The path must be relative and start with a `./`.
306
+ raise ArgumentError, "require_tree argument must be a relative path"
307
+ end
308
+ end
309
+
310
+ # Allows you to state a dependency on a file without
311
+ # including it.
312
+ #
313
+ # This is used for caching purposes. Any changes made to
314
+ # the dependency file will invalidate the cache of the
315
+ # source file.
316
+ #
317
+ # This is useful if you are using ERB and File.read to pull
318
+ # in contents from another file.
319
+ #
320
+ # //= depend_on "foo.png"
321
+ #
322
+ def process_depend_on_directive(path)
323
+ context.depend_on(path)
324
+ end
325
+
326
+ # Allows you to state a dependency on an asset without including
327
+ # it.
328
+ #
329
+ # This is used for caching purposes. Any changes that would
330
+ # invalid the asset dependency will invalidate the cache our the
331
+ # source file.
332
+ #
333
+ # Unlike `depend_on`, the path must be a requirable asset.
334
+ #
335
+ # //= depend_on_asset "bar.js"
336
+ #
337
+ def process_depend_on_asset_directive(path)
338
+ context.depend_on_asset(path)
339
+ end
340
+
341
+ # Enable Sprockets 1.x compat mode.
342
+ #
343
+ # Makes it possible to use the same JavaScript source
344
+ # file in both Sprockets 1 and 2.
345
+ #
346
+ # //= compat
347
+ #
348
+ def process_compat_directive
349
+ @compat = true
350
+ end
351
+
352
+ # Checks if Sprockets 1.x compat mode enabled
353
+ def compat?
354
+ @compat
355
+ end
356
+
357
+ # Sprockets 1.x allowed for constant interpolation if a
358
+ # constants.yml was present. This is only available if
359
+ # compat mode is on.
360
+ def constants
361
+ if compat?
362
+ path = File.join(context.root_path, "constants.yml")
363
+ File.exist?(path) ? YAML.load_file(path) : {}
364
+ else
365
+ {}
366
+ end
367
+ end
368
+
369
+ # `provide` is stubbed out for Sprockets 1.x compat.
370
+ # Mutating the path when an asset is being built is
371
+ # not permitted.
372
+ def process_provide_directive(path)
373
+ end
374
+
375
+ private
376
+ def relative?(path)
377
+ path =~ /^\.($|\.?\/)/
378
+ end
379
+ end
380
+ end
@@ -0,0 +1,38 @@
1
+ require 'tilt'
2
+
3
+ module Sprockets
4
+ # Tilt engine class for the Eco compiler. Depends on the `eco` gem.
5
+ #
6
+ # For more infomation see:
7
+ #
8
+ # https://github.com/sstephenson/ruby-eco
9
+ # https://github.com/sstephenson/eco
10
+ #
11
+ class EcoTemplate < Tilt::Template
12
+ # Check to see if Eco is loaded
13
+ def self.engine_initialized?
14
+ defined? ::Eco
15
+ end
16
+
17
+ # Autoload eco library. If the library isn't loaded, Tilt will produce
18
+ # a thread safetly warning. If you intend to use `.eco` files, you
19
+ # should explicitly require it.
20
+ def initialize_engine
21
+ require_template_library 'eco'
22
+ end
23
+
24
+ def prepare
25
+ end
26
+
27
+ # Compile template data with Eco compiler.
28
+ #
29
+ # Returns a JS function definition String. The result should be
30
+ # assigned to a JS variable.
31
+ #
32
+ # # => "function(...) {...}"
33
+ #
34
+ def evaluate(scope, locals, &block)
35
+ Eco.compile(data)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ require 'tilt'
2
+
3
+ module Sprockets
4
+ # Tilt engine class for the EJS compiler. Depends on the `ejs` gem.
5
+ #
6
+ # For more infomation see:
7
+ #
8
+ # https://github.com/sstephenson/ruby-ejs
9
+ #
10
+ class EjsTemplate < Tilt::Template
11
+ # Check to see if EJS is loaded
12
+ def self.engine_initialized?
13
+ defined? ::EJS
14
+ end
15
+
16
+ # Autoload ejs library. If the library isn't loaded, Tilt will produce
17
+ # a thread safetly warning. If you intend to use `.ejs` files, you
18
+ # should explicitly require it.
19
+ def initialize_engine
20
+ require_template_library 'ejs'
21
+ end
22
+
23
+ def prepare
24
+ end
25
+
26
+ # Compile template data with EJS compiler.
27
+ #
28
+ # Returns a JS function definition String. The result should be
29
+ # assigned to a JS variable.
30
+ #
31
+ # # => "function(obj){...}"
32
+ #
33
+ def evaluate(scope, locals, &block)
34
+ EJS.compile(data)
35
+ end
36
+ end
37
+ end