sprockets 3.7.2 → 4.0.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -267
  3. data/README.md +477 -321
  4. data/bin/sprockets +11 -7
  5. data/lib/rake/sprocketstask.rb +3 -2
  6. data/lib/sprockets.rb +99 -39
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +31 -23
  9. data/lib/sprockets/autoload.rb +5 -0
  10. data/lib/sprockets/autoload/babel.rb +8 -0
  11. data/lib/sprockets/autoload/closure.rb +1 -0
  12. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  13. data/lib/sprockets/autoload/eco.rb +1 -0
  14. data/lib/sprockets/autoload/ejs.rb +1 -0
  15. data/lib/sprockets/autoload/jsminc.rb +8 -0
  16. data/lib/sprockets/autoload/sass.rb +1 -0
  17. data/lib/sprockets/autoload/sassc.rb +8 -0
  18. data/lib/sprockets/autoload/uglifier.rb +1 -0
  19. data/lib/sprockets/autoload/yui.rb +1 -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 +49 -12
  23. data/lib/sprockets/bower.rb +5 -2
  24. data/lib/sprockets/bundle.rb +40 -4
  25. data/lib/sprockets/cache.rb +36 -1
  26. data/lib/sprockets/cache/file_store.rb +25 -3
  27. data/lib/sprockets/cache/memory_store.rb +9 -0
  28. data/lib/sprockets/cache/null_store.rb +8 -0
  29. data/lib/sprockets/cached_environment.rb +14 -19
  30. data/lib/sprockets/closure_compressor.rb +1 -0
  31. data/lib/sprockets/coffee_script_processor.rb +18 -4
  32. data/lib/sprockets/compressing.rb +43 -3
  33. data/lib/sprockets/configuration.rb +3 -7
  34. data/lib/sprockets/context.rb +97 -24
  35. data/lib/sprockets/dependencies.rb +1 -0
  36. data/lib/sprockets/digest_utils.rb +25 -5
  37. data/lib/sprockets/directive_processor.rb +45 -35
  38. data/lib/sprockets/eco_processor.rb +1 -0
  39. data/lib/sprockets/ejs_processor.rb +1 -0
  40. data/lib/sprockets/encoding_utils.rb +1 -0
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +28 -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 +87 -67
  54. data/lib/sprockets/manifest.rb +64 -62
  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 +87 -7
  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 +31 -61
  64. data/lib/sprockets/processor_utils.rb +24 -35
  65. data/lib/sprockets/resolve.rb +177 -93
  66. data/lib/sprockets/sass_cache_store.rb +2 -6
  67. data/lib/sprockets/sass_compressor.rb +13 -1
  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 +30 -9
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +26 -23
  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 +21 -11
  78. data/lib/sprockets/unloaded_asset.rb +13 -11
  79. data/lib/sprockets/uri_tar.rb +1 -0
  80. data/lib/sprockets/uri_utils.rb +11 -8
  81. data/lib/sprockets/utils.rb +41 -74
  82. data/lib/sprockets/utils/gzip.rb +46 -14
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +1 -0
  85. metadata +127 -23
  86. data/LICENSE +0 -21
  87. data/lib/sprockets/coffee_script_template.rb +0 -17
  88. data/lib/sprockets/deprecation.rb +0 -90
  89. data/lib/sprockets/eco_template.rb +0 -17
  90. data/lib/sprockets/ejs_template.rb +0 -17
  91. data/lib/sprockets/engines.rb +0 -92
  92. data/lib/sprockets/erb_template.rb +0 -11
  93. data/lib/sprockets/legacy.rb +0 -330
  94. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  95. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  96. data/lib/sprockets/sass_template.rb +0 -19
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  require 'json'
2
3
  require 'time'
3
4
 
4
5
  require 'concurrent'
5
6
 
6
7
  require 'sprockets/manifest_utils'
7
- require 'sprockets/utils/gzip'
8
8
 
9
9
  module Sprockets
10
10
  # The Manifest logs the contents of assets compiled to a single directory. It
@@ -52,14 +52,8 @@ module Sprockets
52
52
  @directory ||= File.dirname(@filename) if @filename
53
53
 
54
54
  # If directory is given w/o filename, pick a random manifest location
55
- @rename_filename = nil
56
55
  if @directory && @filename.nil?
57
- @filename = find_directory_manifest(@directory)
58
-
59
- # If legacy manifest name autodetected, mark to rename on save
60
- if File.basename(@filename).start_with?("manifest")
61
- @rename_filename = File.join(@directory, generate_manifest_path)
62
- end
56
+ @filename = find_directory_manifest(@directory, logger)
63
57
  end
64
58
 
65
59
  unless @directory && @filename
@@ -125,26 +119,15 @@ module Sprockets
125
119
 
126
120
  return to_enum(__method__, *args) unless block_given?
127
121
 
128
- paths, filters = args.flatten.partition { |arg| self.class.simple_logical_path?(arg) }
129
- filters = filters.map { |arg| self.class.compile_match_filter(arg) }
130
-
131
122
  environment = self.environment.cached
132
-
133
- paths.each do |path|
134
- environment.find_all_linked_assets(path) do |asset|
135
- yield asset
136
- end
137
- end
138
-
139
- if filters.any?
140
- environment.logical_paths do |logical_path, filename|
141
- if filters.any? { |f| f.call(logical_path, filename) }
142
- environment.find_all_linked_assets(filename) do |asset|
143
- yield asset
144
- end
123
+ promises = args.flatten.map do |path|
124
+ Concurrent::Promise.execute(executor: executor) do
125
+ environment.find_all_linked_assets(path) do |asset|
126
+ yield asset
145
127
  end
146
128
  end
147
129
  end
130
+ promises.each(&:wait!)
148
131
 
149
132
  nil
150
133
  end
@@ -167,7 +150,7 @@ module Sprockets
167
150
  end
168
151
  end
169
152
 
170
- # Compile and write asset to directory. The asset is written to a
153
+ # Compile asset to directory. The asset is written to a
171
154
  # fingerprinted filename like
172
155
  # `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
173
156
  # also inserted into the manifest file.
@@ -179,14 +162,19 @@ module Sprockets
179
162
  raise Error, "manifest requires environment for compilation"
180
163
  end
181
164
 
182
- filenames = []
183
- concurrent_compressors = []
184
- concurrent_writers = []
165
+ filenames = []
166
+ concurrent_exporters = []
185
167
 
168
+ assets_to_export = Concurrent::Array.new
186
169
  find(*args) do |asset|
170
+ assets_to_export << asset
171
+ end
172
+
173
+ assets_to_export.each do |asset|
174
+ mtime = Time.now.iso8601
187
175
  files[asset.digest_path] = {
188
176
  'logical_path' => asset.logical_path,
189
- 'mtime' => asset.mtime.iso8601,
177
+ 'mtime' => mtime,
190
178
  'size' => asset.bytesize,
191
179
  'digest' => asset.hexdigest,
192
180
 
@@ -197,38 +185,23 @@ module Sprockets
197
185
  }
198
186
  assets[asset.logical_path] = asset.digest_path
199
187
 
200
- if alias_logical_path = self.class.compute_alias_logical_path(asset.logical_path)
201
- assets[alias_logical_path] = asset.digest_path
202
- end
203
-
204
- target = File.join(dir, asset.digest_path)
205
-
206
- if File.exist?(target)
207
- logger.debug "Skipping #{target}, already exists"
208
- else
209
- logger.info "Writing #{target}"
210
- write_file = Concurrent::Future.execute { asset.write_to target }
211
- concurrent_writers << write_file
212
- end
213
188
  filenames << asset.filename
214
189
 
215
- next if environment.skip_gzip?
216
- gzip = Utils::Gzip.new(asset)
217
- next if gzip.cannot_compress?(environment.mime_types)
190
+ promise = nil
191
+ exporters_for_asset(asset) do |exporter|
192
+ next if exporter.skip?(logger)
218
193
 
219
- if File.exist?("#{target}.gz")
220
- logger.debug "Skipping #{target}.gz, already exists"
221
- else
222
- logger.info "Writing #{target}.gz"
223
- concurrent_compressors << Concurrent::Future.execute do
224
- write_file.wait! if write_file
225
- gzip.compress(target)
194
+ if promise.nil?
195
+ promise = Concurrent::Promise.new(executor: executor) { exporter.call }
196
+ concurrent_exporters << promise.execute
197
+ else
198
+ concurrent_exporters << promise.then { exporter.call }
226
199
  end
227
200
  end
228
-
229
201
  end
230
- concurrent_writers.each(&:wait!)
231
- concurrent_compressors.each(&:wait!)
202
+
203
+ # make sure all exporters have finished before returning the main thread
204
+ concurrent_exporters.each(&:wait!)
232
205
  save
233
206
 
234
207
  filenames
@@ -295,18 +268,13 @@ module Sprockets
295
268
  def clobber
296
269
  FileUtils.rm_r(directory) if File.exist?(directory)
297
270
  logger.info "Removed #{directory}"
271
+ # if we have an environment clear the cache too
272
+ environment.cache.clear if environment
298
273
  nil
299
274
  end
300
275
 
301
276
  # Persist manfiest back to FS
302
277
  def save
303
- if @rename_filename
304
- logger.info "Renaming #{@filename} to #{@rename_filename}"
305
- FileUtils.mv(@filename, @rename_filename)
306
- @filename = @rename_filename
307
- @rename_filename = nil
308
- end
309
-
310
278
  data = json_encode(@data)
311
279
  FileUtils.mkdir_p File.dirname(@filename)
312
280
  PathUtils.atomic_write(@filename) do |f|
@@ -315,6 +283,36 @@ module Sprockets
315
283
  end
316
284
 
317
285
  private
286
+
287
+ # Given an asset, finds all exporters that
288
+ # match its mime-type.
289
+ #
290
+ # Will yield each expoter to the passed in block.
291
+ #
292
+ # array = []
293
+ # puts asset.content_type # => "application/javascript"
294
+ # exporters_for_asset(asset) do |exporter|
295
+ # array << exporter
296
+ # end
297
+ # # puts array => [Exporters::FileExporter, Exporters::ZlibExporter]
298
+ def exporters_for_asset(asset)
299
+ exporters = [Exporters::FileExporter]
300
+
301
+ environment.exporters.each do |mime_type, exporter_list|
302
+ next unless asset.content_type
303
+ next unless environment.match_mime_type? asset.content_type, mime_type
304
+ exporter_list.each do |exporter|
305
+ exporters << exporter
306
+ end
307
+ end
308
+
309
+ exporters.uniq!
310
+
311
+ exporters.each do |exporter|
312
+ yield exporter.new(asset: asset, environment: environment, directory: dir)
313
+ end
314
+ end
315
+
318
316
  def json_decode(obj)
319
317
  JSON.parse(obj, create_additions: false)
320
318
  end
@@ -332,5 +330,9 @@ module Sprockets
332
330
  logger
333
331
  end
334
332
  end
333
+
334
+ def executor
335
+ @executor ||= environment.export_concurrent ? :fast : :immediate
336
+ end
335
337
  end
336
338
  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 candiate 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,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Sprockets
2
3
  # Internal: File and path related utilities. Mixed into Environment.
3
4
  #
@@ -5,6 +6,7 @@ module Sprockets
5
6
  # when code actually wants to reference ::FileUtils.
6
7
  module PathUtils
7
8
  extend self
9
+ require 'pathname'
8
10
 
9
11
  # Public: Like `File.stat`.
10
12
  #
@@ -53,13 +55,14 @@ module Sprockets
53
55
  # Returns an empty `Array` if the directory does not exist.
54
56
  def entries(path)
55
57
  if File.directory?(path)
56
- entries = Dir.entries(path, :encoding => Encoding.default_internal)
58
+ entries = Dir.entries(path, encoding: Encoding.default_internal)
57
59
  entries.reject! { |entry|
58
60
  entry.start_with?(".".freeze) ||
59
61
  (entry.start_with?("#".freeze) && entry.end_with?("#".freeze)) ||
60
62
  entry.end_with?("~".freeze)
61
63
  }
62
64
  entries.sort!
65
+ entries
63
66
  else
64
67
  []
65
68
  end
@@ -71,8 +74,6 @@ module Sprockets
71
74
  #
72
75
  # Returns true if path is absolute, otherwise false.
73
76
  if File::ALT_SEPARATOR
74
- require 'pathname'
75
-
76
77
  # On Windows, ALT_SEPARATOR is \
77
78
  # Delegate to Pathname since the logic gets complex.
78
79
  def absolute_path?(path)
@@ -80,7 +81,7 @@ module Sprockets
80
81
  end
81
82
  else
82
83
  def absolute_path?(path)
83
- path[0] == File::SEPARATOR
84
+ path.start_with?(File::SEPARATOR)
84
85
  end
85
86
  end
86
87
 
@@ -97,7 +98,58 @@ module Sprockets
97
98
  #
98
99
  # Returns true if path is relative, otherwise false.
99
100
  def relative_path?(path)
100
- 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}"
101
153
  end
102
154
 
103
155
  # Internal: Get relative path for root path and subpath.
@@ -109,7 +161,7 @@ module Sprockets
109
161
  # subpath is outside of path.
110
162
  def split_subpath(path, subpath)
111
163
  return "" if path == subpath
112
- path = File.join(path, '')
164
+ path = File.join(path, ''.freeze)
113
165
  if subpath.start_with?(path)
114
166
  subpath[path.length..-1]
115
167
  else
@@ -163,6 +215,34 @@ module Sprockets
163
215
  nil
164
216
  end
165
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
241
+ end
242
+ end
243
+ matches
244
+ end
245
+
166
246
  # Internal: Returns all parents for path
167
247
  #
168
248
  # path - String absolute filename or directory
@@ -272,7 +352,7 @@ module Sprockets
272
352
  Thread.current.object_id,
273
353
  Process.pid,
274
354
  rand(1000000)
275
- ].join('.')
355
+ ].join('.'.freeze)
276
356
  tmpname = File.join(dirname, basename)
277
357
 
278
358
  File.open(tmpname, 'wb+') do |f|