sprockets 4.0.1

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 (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +665 -0
  4. data/bin/sprockets +93 -0
  5. data/lib/rake/sprocketstask.rb +153 -0
  6. data/lib/sprockets.rb +229 -0
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +202 -0
  9. data/lib/sprockets/autoload.rb +16 -0
  10. data/lib/sprockets/autoload/babel.rb +8 -0
  11. data/lib/sprockets/autoload/closure.rb +8 -0
  12. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  13. data/lib/sprockets/autoload/eco.rb +8 -0
  14. data/lib/sprockets/autoload/ejs.rb +8 -0
  15. data/lib/sprockets/autoload/jsminc.rb +8 -0
  16. data/lib/sprockets/autoload/sass.rb +8 -0
  17. data/lib/sprockets/autoload/sassc.rb +8 -0
  18. data/lib/sprockets/autoload/uglifier.rb +8 -0
  19. data/lib/sprockets/autoload/yui.rb +8 -0
  20. data/lib/sprockets/autoload/zopfli.rb +7 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +147 -0
  23. data/lib/sprockets/bower.rb +61 -0
  24. data/lib/sprockets/bundle.rb +105 -0
  25. data/lib/sprockets/cache.rb +271 -0
  26. data/lib/sprockets/cache/file_store.rb +208 -0
  27. data/lib/sprockets/cache/memory_store.rb +75 -0
  28. data/lib/sprockets/cache/null_store.rb +54 -0
  29. data/lib/sprockets/cached_environment.rb +64 -0
  30. data/lib/sprockets/closure_compressor.rb +48 -0
  31. data/lib/sprockets/coffee_script_processor.rb +39 -0
  32. data/lib/sprockets/compressing.rb +134 -0
  33. data/lib/sprockets/configuration.rb +79 -0
  34. data/lib/sprockets/context.rb +304 -0
  35. data/lib/sprockets/dependencies.rb +74 -0
  36. data/lib/sprockets/digest_utils.rb +200 -0
  37. data/lib/sprockets/directive_processor.rb +414 -0
  38. data/lib/sprockets/eco_processor.rb +33 -0
  39. data/lib/sprockets/ejs_processor.rb +32 -0
  40. data/lib/sprockets/encoding_utils.rb +262 -0
  41. data/lib/sprockets/environment.rb +46 -0
  42. data/lib/sprockets/erb_processor.rb +37 -0
  43. data/lib/sprockets/errors.rb +12 -0
  44. data/lib/sprockets/exporters/base.rb +71 -0
  45. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  46. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  47. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  48. data/lib/sprockets/exporting.rb +73 -0
  49. data/lib/sprockets/file_reader.rb +16 -0
  50. data/lib/sprockets/http_utils.rb +135 -0
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +50 -0
  53. data/lib/sprockets/loader.rb +345 -0
  54. data/lib/sprockets/manifest.rb +338 -0
  55. data/lib/sprockets/manifest_utils.rb +48 -0
  56. data/lib/sprockets/mime.rb +96 -0
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +77 -0
  59. data/lib/sprockets/path_digest_utils.rb +48 -0
  60. data/lib/sprockets/path_utils.rb +367 -0
  61. data/lib/sprockets/paths.rb +82 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +228 -0
  64. data/lib/sprockets/processor_utils.rb +169 -0
  65. data/lib/sprockets/resolve.rb +295 -0
  66. data/lib/sprockets/sass_cache_store.rb +30 -0
  67. data/lib/sprockets/sass_compressor.rb +63 -0
  68. data/lib/sprockets/sass_functions.rb +3 -0
  69. data/lib/sprockets/sass_importer.rb +3 -0
  70. data/lib/sprockets/sass_processor.rb +313 -0
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +295 -0
  74. data/lib/sprockets/source_map_processor.rb +66 -0
  75. data/lib/sprockets/source_map_utils.rb +483 -0
  76. data/lib/sprockets/transformers.rb +173 -0
  77. data/lib/sprockets/uglifier_compressor.rb +66 -0
  78. data/lib/sprockets/unloaded_asset.rb +139 -0
  79. data/lib/sprockets/uri_tar.rb +99 -0
  80. data/lib/sprockets/uri_utils.rb +191 -0
  81. data/lib/sprockets/utils.rb +202 -0
  82. data/lib/sprockets/utils/gzip.rb +99 -0
  83. data/lib/sprockets/version.rb +4 -0
  84. data/lib/sprockets/yui_compressor.rb +56 -0
  85. metadata +444 -0
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/digest_utils'
3
+ require 'sprockets/path_digest_utils'
4
+ require 'sprockets/uri_utils'
5
+
6
+ module Sprockets
7
+ # `Dependencies` is an internal mixin whose public methods are exposed on the
8
+ # `Environment` and `CachedEnvironment` classes.
9
+ module Dependencies
10
+ include DigestUtils, PathDigestUtils, URIUtils
11
+
12
+ # Public: Mapping dependency schemes to resolver functions.
13
+ #
14
+ # key - String scheme
15
+ # value - Proc.call(Environment, String)
16
+ #
17
+ # Returns Hash.
18
+ def dependency_resolvers
19
+ config[:dependency_resolvers]
20
+ end
21
+
22
+ # Public: Default set of dependency URIs for assets.
23
+ #
24
+ # Returns Set of String URIs.
25
+ def dependencies
26
+ config[:dependencies]
27
+ end
28
+
29
+ # Public: Register new dependency URI resolver.
30
+ #
31
+ # scheme - String scheme
32
+ # block -
33
+ # environment - Environment
34
+ # uri - String dependency URI
35
+ #
36
+ # Returns nothing.
37
+ def register_dependency_resolver(scheme, &block)
38
+ self.config = hash_reassoc(config, :dependency_resolvers) do |hash|
39
+ hash.merge(scheme => block)
40
+ end
41
+ end
42
+
43
+ # Public: Add environmental dependency inheirted by all assets.
44
+ #
45
+ # uri - String dependency URI
46
+ #
47
+ # Returns nothing.
48
+ def add_dependency(uri)
49
+ self.config = hash_reassoc(config, :dependencies) do |set|
50
+ set + Set.new([uri])
51
+ end
52
+ end
53
+ alias_method :depend_on, :add_dependency
54
+
55
+ # Internal: Resolve dependency URIs.
56
+ #
57
+ # Returns resolved Object.
58
+ def resolve_dependency(str)
59
+ # Optimize for the most common scheme to
60
+ # save 22k allocations on an average Spree app.
61
+ scheme = if str.start_with?('file-digest:'.freeze)
62
+ 'file-digest'.freeze
63
+ else
64
+ str[/([^:]+)/, 1]
65
+ end
66
+
67
+ if resolver = config[:dependency_resolvers][scheme]
68
+ resolver.call(self, str)
69
+ else
70
+ nil
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+ require 'digest/md5'
3
+ require 'digest/sha1'
4
+ require 'digest/sha2'
5
+ require 'set'
6
+
7
+ module Sprockets
8
+ # Internal: Hash functions and digest related utilities. Mixed into
9
+ # Environment.
10
+ module DigestUtils
11
+ extend self
12
+
13
+ # Internal: Default digest class.
14
+ #
15
+ # Returns a Digest::Base subclass.
16
+ def digest_class
17
+ Digest::SHA256
18
+ end
19
+
20
+ # Internal: Maps digest bytesize to the digest class.
21
+ DIGEST_SIZES = {
22
+ 16 => Digest::MD5,
23
+ 20 => Digest::SHA1,
24
+ 32 => Digest::SHA256,
25
+ 48 => Digest::SHA384,
26
+ 64 => Digest::SHA512
27
+ }
28
+
29
+ # Internal: Detect digest class hash algorithm for digest bytes.
30
+ #
31
+ # While not elegant, all the supported digests have a unique bytesize.
32
+ #
33
+ # Returns Digest::Base or nil.
34
+ def detect_digest_class(bytes)
35
+ DIGEST_SIZES[bytes.bytesize]
36
+ end
37
+
38
+ ADD_VALUE_TO_DIGEST = {
39
+ String => ->(val, digest) { digest << val },
40
+ FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze },
41
+ TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze },
42
+ NilClass => ->(val, digest) { digest << 'NilClass'.freeze },
43
+
44
+ Symbol => ->(val, digest) {
45
+ digest << 'Symbol'.freeze
46
+ digest << val.to_s
47
+ },
48
+ Integer => ->(val, digest) {
49
+ digest << 'Integer'.freeze
50
+ digest << val.to_s
51
+ },
52
+ Array => ->(val, digest) {
53
+ digest << 'Array'.freeze
54
+ val.each do |element|
55
+ ADD_VALUE_TO_DIGEST[element.class].call(element, digest)
56
+ end
57
+ },
58
+ Hash => ->(val, digest) {
59
+ digest << 'Hash'.freeze
60
+ val.sort.each do |array|
61
+ ADD_VALUE_TO_DIGEST[Array].call(array, digest)
62
+ end
63
+ },
64
+ Set => ->(val, digest) {
65
+ digest << 'Set'.freeze
66
+ ADD_VALUE_TO_DIGEST[Array].call(val, digest)
67
+ },
68
+ Encoding => ->(val, digest) {
69
+ digest << 'Encoding'.freeze
70
+ digest << val.name
71
+ },
72
+ }
73
+ if 0.class != Integer # Ruby < 2.4
74
+ ADD_VALUE_TO_DIGEST[Fixnum] = ->(val, digest) {
75
+ digest << 'Integer'.freeze
76
+ digest << val.to_s
77
+ }
78
+ ADD_VALUE_TO_DIGEST[Bignum] = ->(val, digest) {
79
+ digest << 'Integer'.freeze
80
+ digest << val.to_s
81
+ }
82
+ end
83
+
84
+ ADD_VALUE_TO_DIGEST.compare_by_identity.rehash
85
+
86
+ ADD_VALUE_TO_DIGEST.default_proc = ->(_, val) {
87
+ raise TypeError, "couldn't digest #{ val }"
88
+ }
89
+ private_constant :ADD_VALUE_TO_DIGEST
90
+
91
+ # Internal: Generate a hexdigest for a nested JSON serializable object.
92
+ #
93
+ # This is used for generating cache keys, so its pretty important its
94
+ # wicked fast. Microbenchmarks away!
95
+ #
96
+ # obj - A JSON serializable object.
97
+ #
98
+ # Returns a String digest of the object.
99
+ def digest(obj)
100
+ build_digest(obj).digest
101
+ end
102
+
103
+ # Internal: Generate a hexdigest for a nested JSON serializable object.
104
+ #
105
+ # The same as `pack_hexdigest(digest(obj))`.
106
+ #
107
+ # obj - A JSON serializable object.
108
+ #
109
+ # Returns a String digest of the object.
110
+ def hexdigest(obj)
111
+ build_digest(obj).hexdigest!
112
+ end
113
+
114
+ # Internal: Pack a binary digest to a hex encoded string.
115
+ #
116
+ # bin - String bytes
117
+ #
118
+ # Returns hex String.
119
+ def pack_hexdigest(bin)
120
+ bin.unpack('H*'.freeze).first
121
+ end
122
+
123
+ # Internal: Unpack a hex encoded digest string into binary bytes.
124
+ #
125
+ # hex - String hex
126
+ #
127
+ # Returns binary String.
128
+ def unpack_hexdigest(hex)
129
+ [hex].pack('H*')
130
+ end
131
+
132
+ # Internal: Pack a binary digest to a base64 encoded string.
133
+ #
134
+ # bin - String bytes
135
+ #
136
+ # Returns base64 String.
137
+ def pack_base64digest(bin)
138
+ [bin].pack('m0')
139
+ end
140
+
141
+ # Internal: Pack a binary digest to a urlsafe base64 encoded string.
142
+ #
143
+ # bin - String bytes
144
+ #
145
+ # Returns urlsafe base64 String.
146
+ def pack_urlsafe_base64digest(bin)
147
+ str = pack_base64digest(bin)
148
+ str.tr!('+/'.freeze, '-_'.freeze)
149
+ str.tr!('='.freeze, ''.freeze)
150
+ str
151
+ end
152
+
153
+ # Internal: Maps digest class to the CSP hash algorithm name.
154
+ HASH_ALGORITHMS = {
155
+ Digest::SHA256 => 'sha256'.freeze,
156
+ Digest::SHA384 => 'sha384'.freeze,
157
+ Digest::SHA512 => 'sha512'.freeze
158
+ }
159
+
160
+ # Public: Generate hash for use in the `integrity` attribute of an asset tag
161
+ # as per the subresource integrity specification.
162
+ #
163
+ # digest - The String byte digest of the asset content.
164
+ #
165
+ # Returns a String or nil if hash algorithm is incompatible.
166
+ def integrity_uri(digest)
167
+ case digest
168
+ when Digest::Base
169
+ digest_class = digest.class
170
+ digest = digest.digest
171
+ when String
172
+ digest_class = DIGEST_SIZES[digest.bytesize]
173
+ else
174
+ raise TypeError, "unknown digest: #{digest.inspect}"
175
+ end
176
+
177
+ if hash_name = HASH_ALGORITHMS[digest_class]
178
+ "#{hash_name}-#{pack_base64digest(digest)}"
179
+ end
180
+ end
181
+
182
+ # Public: Generate hash for use in the `integrity` attribute of an asset tag
183
+ # as per the subresource integrity specification.
184
+ #
185
+ # digest - The String hexbyte digest of the asset content.
186
+ #
187
+ # Returns a String or nil if hash algorithm is incompatible.
188
+ def hexdigest_integrity_uri(hexdigest)
189
+ integrity_uri(unpack_hexdigest(hexdigest))
190
+ end
191
+
192
+ private
193
+ def build_digest(obj)
194
+ digest = digest_class.new
195
+
196
+ ADD_VALUE_TO_DIGEST[obj.class].call(obj, digest)
197
+ digest
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,414 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+ require 'shellwords'
4
+
5
+ module Sprockets
6
+ # The `DirectiveProcessor` is responsible for parsing and evaluating
7
+ # directive comments in a source file.
8
+ #
9
+ # A directive comment starts with a comment prefix, followed by an "=",
10
+ # then the directive name, then any arguments.
11
+ #
12
+ # // JavaScript
13
+ # //= require "foo"
14
+ #
15
+ # # CoffeeScript
16
+ # #= require "bar"
17
+ #
18
+ # /* CSS
19
+ # *= require "baz"
20
+ # */
21
+ #
22
+ # This makes it possible to disable or modify the processor to do whatever
23
+ # you'd like. You could add your own custom directives or invent your own
24
+ # directive syntax.
25
+ #
26
+ # `Environment#processors` includes `DirectiveProcessor` by default.
27
+ #
28
+ # To remove the processor entirely:
29
+ #
30
+ # env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
31
+ # env.unregister_processor('application/javascript', Sprockets::DirectiveProcessor)
32
+ #
33
+ # Then inject your own preprocessor:
34
+ #
35
+ # env.register_processor('text/css', MyProcessor)
36
+ #
37
+ class DirectiveProcessor
38
+ # Directives are denoted by a `=` followed by the name, then
39
+ # argument list.
40
+ #
41
+ # A few different styles are allowed:
42
+ #
43
+ # // =require foo
44
+ # //= require foo
45
+ # //= require "foo"
46
+ #
47
+ DIRECTIVE_PATTERN = /
48
+ ^ \W* = \s* (\w+.*?) (\*\/)? $
49
+ /x
50
+
51
+ def self.instance
52
+ # Default to C comment styles
53
+ @instance ||= new(comments: ["//", ["/*", "*/"]])
54
+ end
55
+
56
+ def self.call(input)
57
+ instance.call(input)
58
+ end
59
+
60
+ def initialize(comments: [])
61
+ @header_pattern = compile_header_pattern(Array(comments))
62
+ end
63
+
64
+ def call(input)
65
+ dup._call(input)
66
+ end
67
+
68
+ def _call(input)
69
+ @environment = input[:environment]
70
+ @uri = input[:uri]
71
+ @filename = input[:filename]
72
+ @dirname = File.dirname(@filename)
73
+ # If loading a source map file like `application.js.map` resolve
74
+ # dependencies using `.js` instead of `.js.map`
75
+ @content_type = SourceMapProcessor.original_content_type(input[:content_type], error_when_not_found: false)
76
+ @required = Set.new(input[:metadata][:required])
77
+ @stubbed = Set.new(input[:metadata][:stubbed])
78
+ @links = Set.new(input[:metadata][:links])
79
+ @dependencies = Set.new(input[:metadata][:dependencies])
80
+ @to_link = Set.new
81
+ @to_load = Set.new
82
+
83
+ data, directives = process_source(input[:data])
84
+ process_directives(directives)
85
+
86
+ {
87
+ data: data,
88
+ required: @required,
89
+ stubbed: @stubbed,
90
+ links: @links,
91
+ to_load: @to_load,
92
+ to_link: @to_link,
93
+ dependencies: @dependencies
94
+ }
95
+ end
96
+
97
+ protected
98
+ # Directives will only be picked up if they are in the header
99
+ # of the source file. C style (/* */), JavaScript (//), and
100
+ # Ruby (#) comments are supported.
101
+ #
102
+ # Directives in comments after the first non-whitespace line
103
+ # of code will not be processed.
104
+ def compile_header_pattern(comments)
105
+ re = comments.map { |c|
106
+ case c
107
+ when String
108
+ "(?:#{Regexp.escape(c)}.*\\n?)+"
109
+ when Array
110
+ "(?:#{Regexp.escape(c[0])}(?m:.*?)#{Regexp.escape(c[1])})"
111
+ else
112
+ raise TypeError, "unknown comment type: #{c.class}"
113
+ end
114
+ }.join("|")
115
+ Regexp.compile("\\A(?:(?m:\\s*)(?:#{re}))+")
116
+ end
117
+
118
+ def process_source(source)
119
+ header = source[@header_pattern, 0] || ""
120
+ body = $' || source
121
+
122
+ header, directives = extract_directives(header)
123
+
124
+ data = +""
125
+ data.force_encoding(body.encoding)
126
+ data << header unless header.empty?
127
+ data << body
128
+ # Ensure body ends in a new line
129
+ data << "\n" if data.length > 0 && data[-1] != "\n"
130
+
131
+ return data, directives
132
+ end
133
+
134
+ # Returns an Array of directive structures. Each structure
135
+ # is an Array with the line number as the first element, the
136
+ # directive name as the second element, followed by any
137
+ # arguments.
138
+ #
139
+ # [[1, "require", "foo"], [2, "require", "bar"]]
140
+ #
141
+ def extract_directives(header)
142
+ processed_header = +""
143
+ directives = []
144
+
145
+ header.lines.each_with_index do |line, index|
146
+ if directive = line[DIRECTIVE_PATTERN, 1]
147
+ name, *args = Shellwords.shellwords(directive)
148
+ if respond_to?("process_#{name}_directive", true)
149
+ directives << [index + 1, name, *args]
150
+ # Replace directive line with a clean break
151
+ line = "\n"
152
+ end
153
+ end
154
+ processed_header << line
155
+ end
156
+
157
+ processed_header.chomp!
158
+ # Ensure header ends in a new line like before it was processed
159
+ processed_header << "\n" if processed_header.length > 0 && header[-1] == "\n"
160
+
161
+ return processed_header, directives
162
+ end
163
+
164
+ # Gathers comment directives in the source and processes them.
165
+ # Any directive method matching `process_*_directive` will
166
+ # automatically be available. This makes it easy to extend the
167
+ # processor.
168
+ #
169
+ # To implement a custom directive called `require_glob`, subclass
170
+ # `Sprockets::DirectiveProcessor`, then add a method called
171
+ # `process_require_glob_directive`.
172
+ #
173
+ # class DirectiveProcessor < Sprockets::DirectiveProcessor
174
+ # def process_require_glob_directive(glob)
175
+ # Dir["#{dirname}/#{glob}"].sort.each do |filename|
176
+ # require(filename)
177
+ # end
178
+ # end
179
+ # end
180
+ #
181
+ # Replace the current processor on the environment with your own:
182
+ #
183
+ # env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
184
+ # env.register_processor('text/css', DirectiveProcessor)
185
+ #
186
+ def process_directives(directives)
187
+ directives.each do |line_number, name, *args|
188
+ begin
189
+ send("process_#{name}_directive", *args)
190
+ rescue Exception => e
191
+ e.set_backtrace(["#{@filename}:#{line_number}"] + e.backtrace)
192
+ raise e
193
+ end
194
+ end
195
+ end
196
+
197
+ # The `require` directive functions similar to Ruby's own `require`.
198
+ # It provides a way to declare a dependency on a file in your path
199
+ # and ensures it's only loaded once before the source file.
200
+ #
201
+ # `require` works with files in the environment path:
202
+ #
203
+ # //= require "foo.js"
204
+ #
205
+ # Extensions are optional. If your source file is ".js", it
206
+ # assumes you are requiring another ".js".
207
+ #
208
+ # //= require "foo"
209
+ #
210
+ # Relative paths work too. Use a leading `./` to denote a relative
211
+ # path:
212
+ #
213
+ # //= require "./bar"
214
+ #
215
+ def process_require_directive(path)
216
+ @required << resolve(path, accept: @content_type, pipeline: :self)
217
+ end
218
+
219
+ # `require_self` causes the body of the current file to be inserted
220
+ # before any subsequent `require` directives. Useful in CSS files, where
221
+ # it's common for the index file to contain global styles that need to
222
+ # be defined before other dependencies are loaded.
223
+ #
224
+ # /*= require "reset"
225
+ # *= require_self
226
+ # *= require_tree .
227
+ # */
228
+ #
229
+ def process_require_self_directive
230
+ if @required.include?(@uri)
231
+ raise ArgumentError, "require_self can only be called once per source file"
232
+ end
233
+ @required << @uri
234
+ end
235
+
236
+ # `require_directory` requires all the files inside a single
237
+ # directory. It's similar to `path/*` since it does not follow
238
+ # nested directories.
239
+ #
240
+ # //= require_directory "./javascripts"
241
+ #
242
+ def process_require_directory_directive(path = ".")
243
+ path = expand_relative_dirname(:require_directory, path)
244
+ require_paths(*@environment.stat_directory_with_dependencies(path))
245
+ end
246
+
247
+ # `require_tree` requires all the nested files in a directory.
248
+ # Its glob equivalent is `path/**/*`.
249
+ #
250
+ # //= require_tree "./public"
251
+ #
252
+ def process_require_tree_directive(path = ".")
253
+ path = expand_relative_dirname(:require_tree, path)
254
+ require_paths(*@environment.stat_sorted_tree_with_dependencies(path))
255
+ end
256
+
257
+ # Allows you to state a dependency on a file without
258
+ # including it.
259
+ #
260
+ # This is used for caching purposes. Any changes made to
261
+ # the dependency file will invalidate the cache of the
262
+ # source file.
263
+ #
264
+ # This is useful if you are using ERB and File.read to pull
265
+ # in contents from another file.
266
+ #
267
+ # //= depend_on "foo.png"
268
+ #
269
+ def process_depend_on_directive(path)
270
+ resolve(path)
271
+ end
272
+
273
+ # Allows you to state a dependency on an asset without including
274
+ # it.
275
+ #
276
+ # This is used for caching purposes. Any changes that would
277
+ # invalidate the asset dependency will invalidate the cache of
278
+ # the source file.
279
+ #
280
+ # Unlike `depend_on`, the path must be a requirable asset.
281
+ #
282
+ # //= depend_on_asset "bar.js"
283
+ #
284
+ def process_depend_on_asset_directive(path)
285
+ to_load(resolve(path))
286
+ end
287
+
288
+ # Allows dependency to be excluded from the asset bundle.
289
+ #
290
+ # The `path` must be a valid asset and may or may not already
291
+ # be part of the bundle. Once stubbed, it is blacklisted and
292
+ # can't be brought back by any other `require`.
293
+ #
294
+ # //= stub "jquery"
295
+ #
296
+ def process_stub_directive(path)
297
+ @stubbed << resolve(path, accept: @content_type, pipeline: :self)
298
+ end
299
+
300
+ # Declares a linked dependency on the target asset.
301
+ #
302
+ # The `path` must be a valid asset and should not already be part of the
303
+ # bundle. Any linked assets will automatically be compiled along with the
304
+ # current.
305
+ #
306
+ # /*= link "logo.png" */
307
+ #
308
+ def process_link_directive(path)
309
+ uri = to_load(resolve(path))
310
+ @to_link << uri
311
+ end
312
+
313
+ # `link_directory` links all the files inside a single
314
+ # directory. It's similar to `path/*` since it does not follow
315
+ # nested directories.
316
+ #
317
+ # //= link_directory "./fonts"
318
+ #
319
+ # Use caution when linking against JS or CSS assets. Include an explicit
320
+ # extension or content type in these cases.
321
+ #
322
+ # //= link_directory "./scripts" .js
323
+ #
324
+ def process_link_directory_directive(path = ".", accept = nil)
325
+ path = expand_relative_dirname(:link_directory, path)
326
+ accept = expand_accept_shorthand(accept)
327
+ link_paths(*@environment.stat_directory_with_dependencies(path), accept)
328
+ end
329
+
330
+ # `link_tree` links all the nested files in a directory.
331
+ # Its glob equivalent is `path/**/*`.
332
+ #
333
+ # //= link_tree "./images"
334
+ #
335
+ # Use caution when linking against JS or CSS assets. Include an explicit
336
+ # extension or content type in these cases.
337
+ #
338
+ # //= link_tree "./styles" .css
339
+ #
340
+ def process_link_tree_directive(path = ".", accept = nil)
341
+ path = expand_relative_dirname(:link_tree, path)
342
+ accept = expand_accept_shorthand(accept)
343
+ link_paths(*@environment.stat_sorted_tree_with_dependencies(path), accept)
344
+ end
345
+
346
+ private
347
+ def expand_accept_shorthand(accept)
348
+ if accept.nil?
349
+ nil
350
+ elsif accept.include?("/")
351
+ accept
352
+ elsif accept.start_with?(".")
353
+ @environment.mime_exts[accept]
354
+ else
355
+ @environment.mime_exts[".#{accept}"]
356
+ end
357
+ end
358
+
359
+ def require_paths(paths, deps)
360
+ resolve_paths(paths, deps, accept: @content_type, pipeline: :self) do |uri|
361
+ @required << uri
362
+ end
363
+ end
364
+
365
+ def link_paths(paths, deps, accept)
366
+ resolve_paths(paths, deps, accept: accept) do |uri|
367
+ @to_link << to_load(uri)
368
+ end
369
+ end
370
+
371
+ def resolve_paths(paths, deps, **kargs)
372
+ @dependencies.merge(deps)
373
+ paths.each do |subpath, stat|
374
+ next if subpath == @filename || stat.directory?
375
+ uri, deps = @environment.resolve(subpath, **kargs)
376
+ @dependencies.merge(deps)
377
+ yield uri if uri
378
+ end
379
+ end
380
+
381
+ def expand_relative_dirname(directive, path)
382
+ if @environment.relative_path?(path)
383
+ path = File.expand_path(path, @dirname)
384
+ stat = @environment.stat(path)
385
+
386
+ if stat && stat.directory?
387
+ path
388
+ else
389
+ raise ArgumentError, "#{directive} argument must be a directory"
390
+ end
391
+ else
392
+ # The path must be relative and start with a `./`.
393
+ raise ArgumentError, "#{directive} argument must be a relative path"
394
+ end
395
+ end
396
+
397
+ def to_load(uri)
398
+ @to_load << uri
399
+ uri
400
+ end
401
+
402
+ def resolve(path, **kargs)
403
+ # Prevent absolute paths in directives
404
+ if @environment.absolute_path?(path)
405
+ raise FileOutsidePaths, "can't require absolute file: #{path}"
406
+ end
407
+
408
+ kargs[:base_path] = @dirname
409
+ uri, deps = @environment.resolve!(path, **kargs)
410
+ @dependencies.merge(deps)
411
+ uri
412
+ end
413
+ end
414
+ end