sprockets 3.0.3 → 4.2.0

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 (95) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +101 -0
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +531 -276
  5. data/bin/sprockets +12 -7
  6. data/lib/rake/sprocketstask.rb +9 -4
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +41 -28
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +1 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  12. data/lib/sprockets/autoload/eco.rb +1 -0
  13. data/lib/sprockets/autoload/ejs.rb +1 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +1 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +1 -0
  18. data/lib/sprockets/autoload/yui.rb +1 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +5 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +61 -13
  23. data/lib/sprockets/bower.rb +6 -3
  24. data/lib/sprockets/bundle.rb +41 -5
  25. data/lib/sprockets/cache/file_store.rb +32 -7
  26. data/lib/sprockets/cache/memory_store.rb +28 -10
  27. data/lib/sprockets/cache/null_store.rb +8 -0
  28. data/lib/sprockets/cache.rb +43 -6
  29. data/lib/sprockets/cached_environment.rb +15 -20
  30. data/lib/sprockets/closure_compressor.rb +6 -11
  31. data/lib/sprockets/coffee_script_processor.rb +20 -6
  32. data/lib/sprockets/compressing.rb +62 -2
  33. data/lib/sprockets/configuration.rb +5 -9
  34. data/lib/sprockets/context.rb +99 -25
  35. data/lib/sprockets/dependencies.rb +10 -9
  36. data/lib/sprockets/digest_utils.rb +103 -62
  37. data/lib/sprockets/directive_processor.rb +64 -36
  38. data/lib/sprockets/eco_processor.rb +4 -3
  39. data/lib/sprockets/ejs_processor.rb +4 -3
  40. data/lib/sprockets/encoding_utils.rb +1 -0
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +34 -21
  43. data/lib/sprockets/errors.rb +1 -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 +1 -0
  50. data/lib/sprockets/http_utils.rb +25 -7
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +11 -10
  53. data/lib/sprockets/loader.rb +244 -62
  54. data/lib/sprockets/manifest.rb +100 -46
  55. data/lib/sprockets/manifest_utils.rb +9 -6
  56. data/lib/sprockets/mime.rb +8 -42
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +3 -11
  59. data/lib/sprockets/path_digest_utils.rb +2 -1
  60. data/lib/sprockets/path_utils.rb +107 -22
  61. data/lib/sprockets/paths.rb +1 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +32 -52
  64. data/lib/sprockets/processor_utils.rb +38 -39
  65. data/lib/sprockets/resolve.rb +177 -97
  66. data/lib/sprockets/sass_cache_store.rb +1 -0
  67. data/lib/sprockets/sass_compressor.rb +21 -17
  68. data/lib/sprockets/sass_functions.rb +1 -0
  69. data/lib/sprockets/sass_importer.rb +1 -0
  70. data/lib/sprockets/sass_processor.rb +46 -18
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +77 -44
  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 +63 -35
  77. data/lib/sprockets/uglifier_compressor.rb +23 -20
  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 +14 -14
  81. data/lib/sprockets/utils/gzip.rb +99 -0
  82. data/lib/sprockets/utils.rb +63 -71
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +5 -14
  85. data/lib/sprockets.rb +105 -33
  86. metadata +157 -27
  87. data/lib/sprockets/coffee_script_template.rb +0 -6
  88. data/lib/sprockets/eco_template.rb +0 -6
  89. data/lib/sprockets/ejs_template.rb +0 -6
  90. data/lib/sprockets/engines.rb +0 -81
  91. data/lib/sprockets/erb_template.rb +0 -6
  92. data/lib/sprockets/legacy.rb +0 -314
  93. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  94. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  95. data/lib/sprockets/sass_template.rb +0 -7
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  require 'json'
2
3
  require 'time'
4
+
5
+ require 'concurrent'
6
+
3
7
  require 'sprockets/manifest_utils'
4
8
 
5
9
  module Sprockets
@@ -11,7 +15,7 @@ module Sprockets
11
15
  # The JSON is part of the public API and should be considered stable. This
12
16
  # should make it easy to read from other programming languages and processes
13
17
  # that don't have sprockets loaded. See `#assets` and `#files` for more
14
- # infomation about the structure.
18
+ # information about the structure.
15
19
  class Manifest
16
20
  include ManifestUtils
17
21
 
@@ -48,14 +52,8 @@ module Sprockets
48
52
  @directory ||= File.dirname(@filename) if @filename
49
53
 
50
54
  # If directory is given w/o filename, pick a random manifest location
51
- @rename_filename = nil
52
55
  if @directory && @filename.nil?
53
- @filename = find_directory_manifest(@directory)
54
-
55
- # If legacy manifest name autodetected, mark to rename on save
56
- if File.basename(@filename).start_with?("manifest")
57
- @rename_filename = File.join(@directory, generate_manifest_path)
58
- end
56
+ @filename = find_directory_manifest(@directory, logger)
59
57
  end
60
58
 
61
59
  unless @directory && @filename
@@ -114,38 +112,46 @@ module Sprockets
114
112
  # Public: Find all assets matching pattern set in environment.
115
113
  #
116
114
  # Returns Enumerator of Assets.
117
- def find(*args)
115
+ def find(*args, &block)
118
116
  unless environment
119
117
  raise Error, "manifest requires environment for compilation"
120
118
  end
121
119
 
122
120
  return to_enum(__method__, *args) unless block_given?
123
121
 
124
- paths, filters = args.flatten.partition { |arg| self.class.simple_logical_path?(arg) }
125
- filters = filters.map { |arg| self.class.compile_match_filter(arg) }
126
-
127
122
  environment = self.environment.cached
128
-
129
- paths.each do |path|
130
- environment.find_all_linked_assets(path) do |asset|
131
- yield asset
123
+ promises = args.flatten.map do |path|
124
+ Concurrent::Promise.execute(executor: executor) do
125
+ environment.find_all_linked_assets(path).to_a
132
126
  end
133
127
  end
134
128
 
135
- if filters.any?
136
- environment.logical_paths do |logical_path, filename|
137
- if filters.any? { |f| f.call(logical_path, filename) }
138
- environment.find_all_linked_assets(filename) do |asset|
139
- yield asset
140
- end
141
- end
142
- end
129
+ promises.each do |promise|
130
+ promise.value!.each(&block)
143
131
  end
144
132
 
145
133
  nil
146
134
  end
147
135
 
148
- # Compile and write asset to directory. The asset is written to a
136
+ # Public: Find the source of assets by paths.
137
+ #
138
+ # Returns Enumerator of assets file content.
139
+ def find_sources(*args)
140
+ return to_enum(__method__, *args) unless block_given?
141
+
142
+ if environment
143
+ find(*args).each do |asset|
144
+ yield asset.source
145
+ end
146
+ else
147
+ args.each do |path|
148
+ asset = assets[path]
149
+ yield File.binread(File.join(dir, asset)) if asset
150
+ end
151
+ end
152
+ end
153
+
154
+ # Compile asset to directory. The asset is written to a
149
155
  # fingerprinted filename like
150
156
  # `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
151
157
  # also inserted into the manifest file.
@@ -157,29 +163,46 @@ module Sprockets
157
163
  raise Error, "manifest requires environment for compilation"
158
164
  end
159
165
 
160
- filenames = []
166
+ filenames = []
167
+ concurrent_exporters = []
161
168
 
169
+ assets_to_export = Concurrent::Array.new
162
170
  find(*args) do |asset|
171
+ assets_to_export << asset
172
+ end
173
+
174
+ assets_to_export.each do |asset|
175
+ mtime = Time.now.iso8601
163
176
  files[asset.digest_path] = {
164
177
  'logical_path' => asset.logical_path,
165
- 'mtime' => asset.mtime.iso8601,
178
+ 'mtime' => mtime,
166
179
  'size' => asset.bytesize,
167
180
  'digest' => asset.hexdigest,
168
- 'integrity' => asset.integrity
181
+
182
+ # Deprecated: Remove beta integrity attribute in next release.
183
+ # Callers should DigestUtils.hexdigest_integrity_uri to compute the
184
+ # digest themselves.
185
+ 'integrity' => DigestUtils.hexdigest_integrity_uri(asset.hexdigest)
169
186
  }
170
187
  assets[asset.logical_path] = asset.digest_path
171
188
 
172
- target = File.join(dir, asset.digest_path)
189
+ filenames << asset.filename
173
190
 
174
- if File.exist?(target)
175
- logger.debug "Skipping #{target}, already exists"
176
- else
177
- logger.info "Writing #{target}"
178
- asset.write_to target
179
- end
191
+ promise = nil
192
+ exporters_for_asset(asset) do |exporter|
193
+ next if exporter.skip?(logger)
180
194
 
181
- filenames << asset.filename
195
+ if promise.nil?
196
+ promise = Concurrent::Promise.new(executor: executor) { exporter.call }
197
+ concurrent_exporters << promise.execute
198
+ else
199
+ concurrent_exporters << promise.then { exporter.call }
200
+ end
201
+ end
182
202
  end
203
+
204
+ # make sure all exporters have finished before returning the main thread
205
+ concurrent_exporters.each(&:wait!)
183
206
  save
184
207
 
185
208
  filenames
@@ -192,6 +215,7 @@ module Sprockets
192
215
  #
193
216
  def remove(filename)
194
217
  path = File.join(dir, filename)
218
+ gzip = "#{path}.gz"
195
219
  logical_path = files[filename]['logical_path']
196
220
 
197
221
  if assets[logical_path] == filename
@@ -200,6 +224,7 @@ module Sprockets
200
224
 
201
225
  files.delete(filename)
202
226
  FileUtils.rm(path) if File.exist?(path)
227
+ FileUtils.rm(gzip) if File.exist?(gzip)
203
228
 
204
229
  save
205
230
 
@@ -230,9 +255,9 @@ module Sprockets
230
255
  # Sort by timestamp
231
256
  Time.parse(attrs['mtime'])
232
257
  }.reverse.each_with_index.drop_while { |(_, attrs), index|
233
- age = [0, Time.now - Time.parse(attrs['mtime'])].max
258
+ _age = [0, Time.now - Time.parse(attrs['mtime'])].max
234
259
  # Keep if under age or within the count limit
235
- age < age || index < count
260
+ _age < age || index < count
236
261
  }.each { |(path, _), _|
237
262
  # Remove old assets
238
263
  remove(path)
@@ -244,18 +269,13 @@ module Sprockets
244
269
  def clobber
245
270
  FileUtils.rm_r(directory) if File.exist?(directory)
246
271
  logger.info "Removed #{directory}"
272
+ # if we have an environment clear the cache too
273
+ environment.cache.clear if environment
247
274
  nil
248
275
  end
249
276
 
250
- # Persist manfiest back to FS
277
+ # Persist manifest back to FS
251
278
  def save
252
- if @rename_filename
253
- logger.info "Renaming #{@filename} to #{@rename_filename}"
254
- FileUtils.mv(@filename, @rename_filename)
255
- @filename = @rename_filename
256
- @rename_filename = nil
257
- end
258
-
259
279
  data = json_encode(@data)
260
280
  FileUtils.mkdir_p File.dirname(@filename)
261
281
  PathUtils.atomic_write(@filename) do |f|
@@ -264,6 +284,36 @@ module Sprockets
264
284
  end
265
285
 
266
286
  private
287
+
288
+ # Given an asset, finds all exporters that
289
+ # match its mime-type.
290
+ #
291
+ # Will yield each expoter to the passed in block.
292
+ #
293
+ # array = []
294
+ # puts asset.content_type # => "application/javascript"
295
+ # exporters_for_asset(asset) do |exporter|
296
+ # array << exporter
297
+ # end
298
+ # # puts array => [Exporters::FileExporter, Exporters::ZlibExporter]
299
+ def exporters_for_asset(asset)
300
+ exporters = [Exporters::FileExporter]
301
+
302
+ environment.exporters.each do |mime_type, exporter_list|
303
+ next unless asset.content_type
304
+ next unless environment.match_mime_type? asset.content_type, mime_type
305
+ exporter_list.each do |exporter|
306
+ exporters << exporter
307
+ end
308
+ end
309
+
310
+ exporters.uniq!
311
+
312
+ exporters.each do |exporter|
313
+ yield exporter.new(asset: asset, environment: environment, directory: dir)
314
+ end
315
+ end
316
+
267
317
  def json_decode(obj)
268
318
  JSON.parse(obj, create_additions: false)
269
319
  end
@@ -281,5 +331,9 @@ module Sprockets
281
331
  logger
282
332
  end
283
333
  end
334
+
335
+ def executor
336
+ @executor ||= environment.export_concurrent ? :fast : :immediate
337
+ end
284
338
  end
285
339
  end
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  require 'securerandom'
3
+ require 'logger'
2
4
 
3
5
  module Sprockets
4
6
  # Public: Manifest utilities.
@@ -6,7 +8,6 @@ module Sprockets
6
8
  extend self
7
9
 
8
10
  MANIFEST_RE = /^\.sprockets-manifest-[0-9a-f]{32}.json$/
9
- LEGACY_MANIFEST_RE = /^manifest(-[0-9a-f]{32})?.json$/
10
11
 
11
12
  # Public: Generate a new random manifest path.
12
13
  #
@@ -33,12 +34,14 @@ module Sprockets
33
34
  # # => "/app/public/assets/.sprockets-manifest-abc123.json"
34
35
  #
35
36
  # Returns String filename.
36
- def find_directory_manifest(dirname)
37
+ def find_directory_manifest(dirname, logger = Logger.new($stderr))
37
38
  entries = File.directory?(dirname) ? Dir.entries(dirname) : []
38
- entry = entries.find { |e| e =~ MANIFEST_RE } ||
39
- # Deprecated: Will be removed in 4.x
40
- entries.find { |e| e =~ LEGACY_MANIFEST_RE } ||
41
- generate_manifest_path
39
+ manifest_entries = entries.select { |e| e =~ MANIFEST_RE }
40
+ if manifest_entries.length > 1
41
+ manifest_entries.sort!
42
+ logger.warn("Found multiple manifests: #{manifest_entries}. Choosing the first alphabetically: #{manifest_entries.first}")
43
+ end
44
+ entry = manifest_entries.first || generate_manifest_path
42
45
  File.join(dirname, entry)
43
46
  end
44
47
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/encoding_utils'
2
3
  require 'sprockets/http_utils'
3
4
  require 'sprockets/utils'
@@ -36,29 +37,18 @@ module Sprockets
36
37
 
37
38
  # Public: Register a new mime type.
38
39
  #
39
- # mime_type - String MIME Type
40
- # options - Hash
41
- # extensions: Array of String extnames
42
- # charset: Proc/Method that detects the charset of a file.
43
- # See EncodingUtils.
40
+ # mime_type - String MIME Type
41
+ # extensions - Array of String extnames
42
+ # charset - Proc/Method that detects the charset of a file.
43
+ # See EncodingUtils.
44
44
  #
45
45
  # Returns nothing.
46
- def register_mime_type(mime_type, options = {})
47
- # Legacy extension argument, will be removed from 4.x
48
- if options.is_a?(String)
49
- options = { extensions: [options] }
50
- end
51
-
52
- extnames = Array(options[:extensions]).map { |extname|
53
- Sprockets::Utils.normalize_extension(extname)
54
- }
46
+ def register_mime_type(mime_type, extensions: [], charset: nil)
47
+ extnames = Array(extensions)
55
48
 
56
- charset = options[:charset]
57
49
  charset ||= :default if mime_type.start_with?('text/')
58
50
  charset = EncodingUtils::CHARSET_DETECT[charset] if charset.is_a?(Symbol)
59
51
 
60
- self.computed_config = {}
61
-
62
52
  self.config = hash_reassoc(config, :mime_exts) do |mime_exts|
63
53
  extnames.each do |extname|
64
54
  mime_exts[extname] = mime_type
@@ -97,34 +87,10 @@ module Sprockets
97
87
  data = File.binread(filename)
98
88
 
99
89
  if detect = mime_type_charset_detecter(content_type)
100
- detect.call(data).encode(Encoding::UTF_8, :universal_newline => true)
90
+ detect.call(data).encode(Encoding::UTF_8, universal_newline: true)
101
91
  else
102
92
  data
103
93
  end
104
94
  end
105
-
106
- private
107
- def extname_map
108
- self.computed_config[:_extnames] ||= compute_extname_map
109
- end
110
-
111
- def compute_extname_map
112
- graph = {}
113
-
114
- ([nil] + pipelines.keys.map(&:to_s)).each do |pipeline|
115
- pipeline_extname = ".#{pipeline}" if pipeline
116
- ([[nil, nil]] + config[:mime_exts].to_a).each do |format_extname, format_type|
117
- 4.times do |n|
118
- config[:engines].keys.permutation(n).each do |engine_extnames|
119
- key = "#{pipeline_extname}#{format_extname}#{engine_extnames.join}"
120
- type = format_type || config[:engine_mime_types][engine_extnames.first]
121
- graph[key] = {type: type, engines: engine_extnames, pipeline: pipeline}
122
- end
123
- end
124
- end
125
- end
126
-
127
- graph
128
- end
129
95
  end
130
96
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+
4
+ module Sprockets
5
+ module Npm
6
+ # Internal: Override resolve_alternates to install package.json behavior.
7
+ #
8
+ # load_path - String environment path
9
+ # logical_path - String path relative to base
10
+ #
11
+ # Returns candidate filenames.
12
+ def resolve_alternates(load_path, logical_path)
13
+ candidates, deps = super
14
+
15
+ dirname = File.join(load_path, logical_path)
16
+
17
+ if directory?(dirname)
18
+ filename = File.join(dirname, 'package.json')
19
+
20
+ if self.file?(filename)
21
+ deps << build_file_digest_uri(filename)
22
+ read_package_directives(dirname, filename) do |path|
23
+ if file?(path)
24
+ candidates << path
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ return candidates, deps
31
+ end
32
+
33
+ # Internal: Read package.json's main and style directives.
34
+ #
35
+ # dirname - String path to component directory.
36
+ # filename - String path to package.json.
37
+ #
38
+ # Returns nothing.
39
+ def read_package_directives(dirname, filename)
40
+ package = JSON.parse(File.read(filename), create_additions: false)
41
+
42
+ case package['main']
43
+ when String
44
+ yield File.expand_path(package['main'], dirname)
45
+ when nil
46
+ yield File.expand_path('index.js', dirname)
47
+ end
48
+
49
+ yield File.expand_path(package['style'], dirname) if package['style']
50
+ end
51
+ end
52
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'set'
2
3
  require 'sprockets/path_utils'
3
4
  require 'sprockets/uri_utils'
@@ -41,7 +42,7 @@ module Sprockets
41
42
  #
42
43
  # Returns an Array of entry names and a Set of dependency URIs.
43
44
  def entries_with_dependencies(path)
44
- return entries(path), file_digest_dependency_set(path)
45
+ return entries(path), Set.new([build_file_digest_uri(path)])
45
46
  end
46
47
 
47
48
  # Internal: List directory filenames and associated Stats under a
@@ -53,16 +54,7 @@ module Sprockets
53
54
  #
54
55
  # Returns an Array of filenames and a Set of dependency URIs.
55
56
  def stat_directory_with_dependencies(dir)
56
- return stat_directory(dir).to_a, file_digest_dependency_set(dir)
57
- end
58
-
59
- # Internal: Returns a set of dependencies for a particular path.
60
- #
61
- # path - String directory path
62
- #
63
- # Returns a Set of dependency URIs.
64
- def file_digest_dependency_set(path)
65
- Set.new([build_file_digest_uri(path)])
57
+ return stat_directory(dir).to_a, Set.new([build_file_digest_uri(dir)])
66
58
  end
67
59
 
68
60
  # Internal: List directory filenames and associated Stats under an entire
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/digest_utils'
2
3
  require 'sprockets/path_utils'
3
4
 
@@ -15,7 +16,7 @@ module Sprockets
15
16
  def stat_digest(path, stat)
16
17
  if stat.directory?
17
18
  # If its a directive, digest the list of filenames
18
- digest_class.digest(self.entries(path).join(','))
19
+ digest_class.digest(self.entries(path).join(','.freeze))
19
20
  elsif stat.file?
20
21
  # If its a file, digest the contents
21
22
  digest_class.file(path.to_s).digest
@@ -1,5 +1,4 @@
1
- require 'fileutils'
2
-
1
+ # frozen_string_literal: true
3
2
  module Sprockets
4
3
  # Internal: File and path related utilities. Mixed into Environment.
5
4
  #
@@ -7,6 +6,7 @@ module Sprockets
7
6
  # when code actually wants to reference ::FileUtils.
8
7
  module PathUtils
9
8
  extend self
9
+ require 'pathname'
10
10
 
11
11
  # Public: Like `File.stat`.
12
12
  #
@@ -55,9 +55,14 @@ module Sprockets
55
55
  # Returns an empty `Array` if the directory does not exist.
56
56
  def entries(path)
57
57
  if File.directory?(path)
58
- Dir.entries(path, :encoding => Encoding.default_internal).reject! { |entry|
59
- entry =~ /^\.|~$|^\#.*\#$/
60
- }.sort!
58
+ entries = Dir.entries(path, encoding: Encoding.default_internal)
59
+ entries.reject! { |entry|
60
+ entry.start_with?(".".freeze) ||
61
+ (entry.start_with?("#".freeze) && entry.end_with?("#".freeze)) ||
62
+ entry.end_with?("~".freeze)
63
+ }
64
+ entries.sort!
65
+ entries
61
66
  else
62
67
  []
63
68
  end
@@ -69,8 +74,6 @@ module Sprockets
69
74
  #
70
75
  # Returns true if path is absolute, otherwise false.
71
76
  if File::ALT_SEPARATOR
72
- require 'pathname'
73
-
74
77
  # On Windows, ALT_SEPARATOR is \
75
78
  # Delegate to Pathname since the logic gets complex.
76
79
  def absolute_path?(path)
@@ -78,7 +81,7 @@ module Sprockets
78
81
  end
79
82
  else
80
83
  def absolute_path?(path)
81
- path[0] == File::SEPARATOR
84
+ path.start_with?(File::SEPARATOR)
82
85
  end
83
86
  end
84
87
 
@@ -95,7 +98,58 @@ module Sprockets
95
98
  #
96
99
  # Returns true if path is relative, otherwise false.
97
100
  def relative_path?(path)
98
- path =~ /^\.\.?($|#{SEPARATOR_PATTERN})/ ? true : false
101
+ path.match?(/^\.\.?($|#{SEPARATOR_PATTERN})/) ? true : false
102
+ end
103
+
104
+ # Public: Get relative path from `start` to `dest`.
105
+ #
106
+ # start - String start path (file or dir)
107
+ # dest - String destination path
108
+ #
109
+ # Returns relative String path from `start` to `dest`
110
+ def relative_path_from(start, dest)
111
+ start, dest = Pathname.new(start), Pathname.new(dest)
112
+ start = start.dirname unless start.directory?
113
+ dest.relative_path_from(start).to_s
114
+ end
115
+
116
+ # Public: Joins path to base path.
117
+ #
118
+ # base - Root path
119
+ # path - Extending path
120
+ #
121
+ # Example
122
+ #
123
+ # join('base/path/', '../file.js')
124
+ # # => 'base/file.js'
125
+ #
126
+ # Returns string path starting from base and ending at path
127
+ def join(base, path)
128
+ (Pathname.new(base) + path).to_s
129
+ end
130
+
131
+ # Public: Sets pipeline for path
132
+ #
133
+ # path - String path
134
+ # extensions - List of file extensions
135
+ # pipeline - Pipeline
136
+ #
137
+ # Examples
138
+ #
139
+ # set_pipeline('path/file.js.erb', config[:mime_exts], config[:pipeline_exts], :source)
140
+ # # => 'path/file.source.js.erb'
141
+ #
142
+ # set_pipeline('path/some.file.source.js.erb', config[:mime_exts], config[:pipeline_exts], :debug)
143
+ # # => 'path/some.file.debug.js.erb'
144
+ #
145
+ # Returns string path with pipeline parsed in
146
+ def set_pipeline(path, mime_exts, pipeline_exts, pipeline)
147
+ extension, _ = match_path_extname(path, mime_exts)
148
+ path.chomp!(extension)
149
+ pipeline_old, _ = match_path_extname(path, pipeline_exts)
150
+ path.chomp!(pipeline_old)
151
+
152
+ "#{path}.#{pipeline}#{extension}"
99
153
  end
100
154
 
101
155
  # Internal: Get relative path for root path and subpath.
@@ -107,8 +161,8 @@ module Sprockets
107
161
  # subpath is outside of path.
108
162
  def split_subpath(path, subpath)
109
163
  return "" if path == subpath
110
- path = File.join(path, '')
111
- if subpath.start_with?(path)
164
+ path = File.join(path, ''.freeze)
165
+ if subpath&.start_with?(path)
112
166
  subpath[path.length..-1]
113
167
  else
114
168
  nil
@@ -146,16 +200,47 @@ module Sprockets
146
200
  #
147
201
  # Returns [String extname, Object value] or nil nothing matched.
148
202
  def match_path_extname(path, extensions)
149
- match, key = nil, ""
150
- path_extnames(path).reverse_each do |extname|
151
- key.prepend(extname)
152
- if value = extensions[key]
153
- match = [key.dup, value]
154
- elsif match
155
- break
203
+ basename = File.basename(path)
204
+
205
+ i = basename.index('.'.freeze)
206
+ while i && i < basename.length - 1
207
+ extname = basename[i..-1]
208
+ if value = extensions[extname]
209
+ return extname, value
210
+ end
211
+
212
+ i = basename.index('.'.freeze, i+1)
213
+ end
214
+
215
+ nil
216
+ end
217
+
218
+ # Internal: Match paths in a directory against available extensions.
219
+ #
220
+ # path - String directory
221
+ # basename - String basename of target file
222
+ # extensions - Hash of String extnames to values
223
+ #
224
+ # Examples
225
+ #
226
+ # exts = { ".js" => "application/javascript" }
227
+ # find_matching_path_for_extensions("app/assets", "application", exts)
228
+ # # => ["app/assets/application.js", "application/javascript"]
229
+ #
230
+ # Returns an Array of [String path, Object value] matches.
231
+ def find_matching_path_for_extensions(path, basename, extensions)
232
+ matches = []
233
+ entries(path).each do |entry|
234
+ next unless File.basename(entry).start_with?(basename)
235
+ extname, value = match_path_extname(entry, extensions)
236
+ if basename == entry.chomp(extname)
237
+ filename = File.join(path, entry)
238
+ if file?(filename)
239
+ matches << [filename, value]
240
+ end
156
241
  end
157
242
  end
158
- match
243
+ matches
159
244
  end
160
245
 
161
246
  # Internal: Returns all parents for path
@@ -267,16 +352,16 @@ module Sprockets
267
352
  Thread.current.object_id,
268
353
  Process.pid,
269
354
  rand(1000000)
270
- ].join('.')
355
+ ].join('.'.freeze)
271
356
  tmpname = File.join(dirname, basename)
272
357
 
273
358
  File.open(tmpname, 'wb+') do |f|
274
359
  yield f
275
360
  end
276
361
 
277
- FileUtils.mv(tmpname, filename)
362
+ File.rename(tmpname, filename)
278
363
  ensure
279
- FileUtils.rm(tmpname) if File.exist?(tmpname)
364
+ File.delete(tmpname) if File.exist?(tmpname)
280
365
  end
281
366
  end
282
367
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/path_utils'
2
3
  require 'sprockets/utils'
3
4