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,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/path_utils'
2
3
  require 'sprockets/utils'
3
4
 
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ module Sprockets
3
+ module Preprocessors
4
+ # Private: Adds a default map to assets when one is not present
5
+ #
6
+ # If the input file already has a source map, it effectively returns the original
7
+ # result. Otherwise it maps 1 for 1 lines original to generated. This is needed
8
+ # Because other generators run after might depend on having a valid source map
9
+ # available.
10
+ class DefaultSourceMap
11
+ def call(input)
12
+ result = { data: input[:data] }
13
+ map = input[:metadata][:map]
14
+ filename = input[:filename]
15
+ load_path = input[:load_path]
16
+ lines = input[:data].lines.length
17
+ basename = File.basename(filename)
18
+ mime_exts = input[:environment].config[:mime_exts]
19
+ pipeline_exts = input[:environment].config[:pipeline_exts]
20
+ if map.nil? || map.empty?
21
+ result[:map] = {
22
+ "version" => 3,
23
+ "file" => PathUtils.split_subpath(load_path, filename),
24
+ "mappings" => default_mappings(lines),
25
+ "sources" => [PathUtils.set_pipeline(basename, mime_exts, pipeline_exts, :source)],
26
+ "names" => []
27
+ }
28
+ else
29
+ result[:map] = map
30
+ end
31
+
32
+ result[:map]["x_sprockets_linecount"] = lines
33
+ return result
34
+ end
35
+
36
+ private
37
+
38
+ def default_mappings(lines)
39
+ if (lines == 0)
40
+ ""
41
+ elsif (lines == 1)
42
+ "AAAA"
43
+ else
44
+ "AAAA;" + "AACA;"*(lines - 2) + "AACA"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,7 +1,5 @@
1
- require 'sprockets/engines'
1
+ # frozen_string_literal: true
2
2
  require 'sprockets/file_reader'
3
- require 'sprockets/legacy_proc_processor'
4
- require 'sprockets/legacy_tilt_processor'
5
3
  require 'sprockets/mime'
6
4
  require 'sprockets/processor_utils'
7
5
  require 'sprockets/uri_utils'
@@ -17,9 +15,14 @@ module Sprockets
17
15
  config[:pipelines]
18
16
  end
19
17
 
18
+ # Registers a pipeline that will be called by `call_processor` method.
20
19
  def register_pipeline(name, proc = nil, &block)
21
20
  proc ||= block
22
21
 
22
+ self.config = hash_reassoc(config, :pipeline_exts) do |pipeline_exts|
23
+ pipeline_exts.merge(".#{name}".freeze => name.to_sym)
24
+ end
25
+
23
26
  self.config = hash_reassoc(config, :pipelines) do |pipelines|
24
27
  pipelines.merge(name.to_sym => proc)
25
28
  end
@@ -43,12 +46,13 @@ module Sprockets
43
46
  #
44
47
  # A block can be passed for to create a shorthand processor.
45
48
  #
46
- # register_preprocessor 'text/css', :my_processor do |context, data|
47
- # data.gsub(...)
49
+ # register_preprocessor 'text/css' do |input|
50
+ # input[:data].gsub(...)
48
51
  # end
49
52
  #
50
53
  def register_preprocessor(*args, &block)
51
54
  register_config_processor(:preprocessors, *args, &block)
55
+ compute_transformers!(self.config[:registered_transformers])
52
56
  end
53
57
  alias_method :register_processor, :register_preprocessor
54
58
 
@@ -58,12 +62,13 @@ module Sprockets
58
62
  #
59
63
  # A block can be passed for to create a shorthand processor.
60
64
  #
61
- # register_postprocessor 'application/javascript', :my_processor do |context, data|
62
- # data.gsub(...)
65
+ # register_postprocessor 'application/javascript' do |input|
66
+ # input[:data].gsub(...)
63
67
  # end
64
68
  #
65
69
  def register_postprocessor(*args, &block)
66
70
  register_config_processor(:postprocessors, *args, &block)
71
+ compute_transformers!(self.config[:registered_transformers])
67
72
  end
68
73
 
69
74
  # Remove Preprocessor `klass` for `mime_type`.
@@ -72,6 +77,7 @@ module Sprockets
72
77
  #
73
78
  def unregister_preprocessor(*args)
74
79
  unregister_config_processor(:preprocessors, *args)
80
+ compute_transformers!(self.config[:registered_transformers])
75
81
  end
76
82
  alias_method :unregister_processor, :unregister_preprocessor
77
83
 
@@ -81,6 +87,7 @@ module Sprockets
81
87
  #
82
88
  def unregister_postprocessor(*args)
83
89
  unregister_config_processor(:postprocessors, *args)
90
+ compute_transformers!(self.config[:registered_transformers])
84
91
  end
85
92
 
86
93
  # Bundle Processors are ran on concatenated assets rather than
@@ -95,8 +102,8 @@ module Sprockets
95
102
  #
96
103
  # A block can be passed for to create a shorthand processor.
97
104
  #
98
- # register_bundle_processor 'application/javascript', :my_processor do |context, data|
99
- # data.gsub(...)
105
+ # register_bundle_processor 'application/javascript' do |input|
106
+ # input[:data].gsub(...)
100
107
  # end
101
108
  #
102
109
  def register_bundle_processor(*args, &block)
@@ -154,46 +161,44 @@ module Sprockets
154
161
  protected
155
162
  def resolve_processors_cache_key_uri(uri)
156
163
  params = parse_uri_query_params(uri[11..-1])
157
- params[:engine_extnames] = params[:engines] ? params[:engines].split(',') : []
158
- processors = processors_for(params[:type], params[:file_type], params[:engine_extnames], params[:pipeline])
164
+ processors = processors_for(params[:type], params[:file_type], params[:pipeline])
159
165
  processors_cache_keys(processors)
160
166
  end
161
167
 
162
- def build_processors_uri(type, file_type, engine_extnames, pipeline)
163
- engines = engine_extnames.join(',') if engine_extnames.any?
168
+ def build_processors_uri(type, file_type, pipeline)
164
169
  query = encode_uri_query_params(
165
170
  type: type,
166
171
  file_type: file_type,
167
- engines: engines,
168
172
  pipeline: pipeline
169
173
  )
170
174
  "processors:#{query}"
171
175
  end
172
176
 
173
- def processors_for(type, file_type, engine_extnames, pipeline)
177
+ def processors_for(type, file_type, pipeline)
174
178
  pipeline ||= :default
175
- config[:pipelines][pipeline.to_sym].call(self, type, file_type, engine_extnames)
179
+ if fn = config[:pipelines][pipeline.to_sym]
180
+ fn.call(self, type, file_type)
181
+ else
182
+ raise Error, "no pipeline: #{pipeline}"
183
+ end
176
184
  end
177
185
 
178
- def default_processors_for(type, file_type, engine_extnames)
186
+ def default_processors_for(type, file_type)
179
187
  bundled_processors = config[:bundle_processors][type]
180
188
  if bundled_processors.any?
181
189
  bundled_processors
182
190
  else
183
- self_processors_for(type, file_type, engine_extnames)
191
+ self_processors_for(type, file_type)
184
192
  end
185
193
  end
186
194
 
187
- def self_processors_for(type, file_type, engine_extnames)
195
+ def self_processors_for(type, file_type)
188
196
  processors = []
189
197
 
190
198
  processors.concat config[:postprocessors][type]
191
-
192
199
  if type != file_type && processor = config[:transformers][file_type][type]
193
200
  processors << processor
194
201
  end
195
-
196
- processors.concat engine_extnames.map { |ext| engines[ext] }
197
202
  processors.concat config[:preprocessors][file_type]
198
203
 
199
204
  if processors.any? || mime_type_charset_detecter(type)
@@ -204,55 +209,20 @@ module Sprockets
204
209
  end
205
210
 
206
211
  private
207
- def register_config_processor(type, mime_type, klass, proc = nil, &block)
208
- proc ||= block
209
- processor = wrap_processor(klass, proc)
212
+ def register_config_processor(type, mime_type, processor = nil, &block)
213
+ processor ||= block
210
214
 
211
215
  self.config = hash_reassoc(config, type, mime_type) do |processors|
212
216
  processors.unshift(processor)
213
217
  processors
214
218
  end
215
-
216
- compute_transformers!
217
219
  end
218
220
 
219
- def unregister_config_processor(type, mime_type, klass)
220
- if klass.is_a?(String) || klass.is_a?(Symbol)
221
- klass = config[type][mime_type].detect do |cls|
222
- cls.respond_to?(:name) && cls.name == "Sprockets::LegacyProcProcessor (#{klass})"
223
- end
224
- end
225
-
221
+ def unregister_config_processor(type, mime_type, processor)
226
222
  self.config = hash_reassoc(config, type, mime_type) do |processors|
227
- processors.delete(klass)
223
+ processors.delete_if { |p| p == processor || p.class == processor }
228
224
  processors
229
225
  end
230
-
231
- compute_transformers!
232
- end
233
-
234
- def deprecate_legacy_processor_interface(interface)
235
- msg = "You are using a deprecated processor interface #{ interface.inspect }.\n" +
236
- "Please update your processor interface:\n" +
237
- "https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors\n"
238
-
239
- Deprecation.new([caller[3]]).warn msg
240
- end
241
-
242
- def wrap_processor(klass, proc)
243
- if !proc
244
- if klass.respond_to?(:call)
245
- klass
246
- else
247
- deprecate_legacy_processor_interface(klass)
248
- LegacyTiltProcessor.new(klass)
249
- end
250
- elsif proc.respond_to?(:arity) && proc.arity == 2
251
- deprecate_legacy_processor_interface(proc)
252
- LegacyProcProcessor.new(klass.to_s, proc)
253
- else
254
- proc
255
- end
256
226
  end
257
227
  end
258
228
  end
@@ -1,9 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  require 'set'
2
3
 
3
4
  module Sprockets
4
5
  # Functional utilities for dealing with Processor functions.
5
6
  #
6
- # A Processor is a general function that my modify or transform an asset as
7
+ # A Processor is a general function that may modify or transform an asset as
7
8
  # part of the pipeline. CoffeeScript to JavaScript conversion, Minification
8
9
  # or Concatenation are all implemented as seperate Processor steps.
9
10
  #
@@ -16,26 +17,34 @@ module Sprockets
16
17
  module ProcessorUtils
17
18
  extend self
18
19
 
20
+ class CompositeProcessor < Struct.new(:processor_strategy, :param, :processors) # :nodoc:
21
+ SINGULAR = lambda { |param, input| ProcessorUtils.call_processor param, input }
22
+ PLURAL = lambda { |param, input| ProcessorUtils.call_processors param, input }
23
+
24
+ def self.create(processors)
25
+ if processors.length == 1
26
+ new SINGULAR, processors.first, processors
27
+ else
28
+ new PLURAL, processors, processors
29
+ end
30
+ end
31
+
32
+ def call(input)
33
+ processor_strategy.call param, input
34
+ end
35
+
36
+ def cache_key
37
+ ProcessorUtils.processors_cache_keys(processors)
38
+ end
39
+ end
40
+
19
41
  # Public: Compose processors in right to left order.
20
42
  #
21
43
  # processors - Array of processors callables
22
44
  #
23
45
  # Returns a composed Proc.
24
46
  def compose_processors(*processors)
25
- context = self
26
-
27
- if processors.length == 1
28
- obj = method(:call_processor).to_proc.curry[processors.first]
29
- else
30
- obj = method(:call_processors).to_proc.curry[processors]
31
- end
32
-
33
- metaclass = (class << obj; self; end)
34
- metaclass.send(:define_method, :cache_key) do
35
- context.processors_cache_keys(processors)
36
- end
37
-
38
- obj
47
+ CompositeProcessor.create processors
39
48
  end
40
49
 
41
50
  # Public: Invoke list of processors in right to left order.
@@ -152,29 +161,9 @@ module Sprockets
152
161
  if !key.instance_of?(Symbol)
153
162
  raise TypeError, "processor metadata[#{key.inspect}] expected to be a Symbol"
154
163
  end
155
-
156
- if !valid_processor_metadata_value?(value)
157
- raise TypeError, "processor metadata[:#{key}] returned a complex type: #{value.inspect}\n" +
158
- "Only #{VALID_METADATA_TYPES.to_a.join(", ")} maybe used."
159
- end
160
164
  end
161
165
 
162
166
  result
163
167
  end
164
-
165
- # Internal: Validate object is in validate metadata whitelist.
166
- #
167
- # value - Any Object
168
- #
169
- # Returns true if class is in whitelist otherwise false.
170
- def valid_processor_metadata_value?(value)
171
- if VALID_METADATA_VALUE_TYPES_HASH[value.class]
172
- true
173
- elsif VALID_METADATA_COMPOUND_TYPES_HASH[value.class]
174
- value.all? { |v| valid_processor_metadata_value?(v) }
175
- else
176
- false
177
- end
178
- end
179
168
  end
180
169
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'set'
2
3
  require 'sprockets/http_utils'
3
4
  require 'sprockets/path_dependency_utils'
@@ -20,27 +21,21 @@ module Sprockets
20
21
  # # => "file:///path/to/app/javascripts/application.coffee?type=application/javascript"
21
22
  #
22
23
  # The String Asset URI is returned or nil if no results are found.
23
- def resolve(path, options = {})
24
- path = path.to_s
25
- paths = options[:load_paths] || config[:paths]
26
- accept = options[:accept]
24
+ def resolve(path, load_paths: config[:paths], accept: nil, pipeline: nil, base_path: nil)
25
+ paths = load_paths
27
26
 
28
27
  if valid_asset_uri?(path)
29
28
  uri, deps = resolve_asset_uri(path)
30
29
  elsif absolute_path?(path)
31
30
  filename, type, deps = resolve_absolute_path(paths, path, accept)
32
31
  elsif relative_path?(path)
33
- filename, type, pipeline, deps = resolve_relative_path(paths, path, options[:base_path], accept)
32
+ filename, type, path_pipeline, deps, index_alias = resolve_relative_path(paths, path, base_path, accept)
34
33
  else
35
- filename, type, pipeline, deps = resolve_logical_path(paths, path, accept)
34
+ filename, type, path_pipeline, deps, index_alias = resolve_logical_path(paths, path, accept)
36
35
  end
37
36
 
38
37
  if filename
39
- params = {}
40
- params[:type] = type if type
41
- params[:pipeline] = pipeline if pipeline
42
- params[:pipeline] = options[:pipeline] if options[:pipeline]
43
- uri = build_asset_uri(filename, params)
38
+ uri = build_asset_uri(filename, type: type, pipeline: pipeline || path_pipeline, index_alias: index_alias)
44
39
  end
45
40
 
46
41
  return uri, deps
@@ -48,19 +43,21 @@ module Sprockets
48
43
 
49
44
  # Public: Same as resolve() but raises a FileNotFound exception instead of
50
45
  # nil if no assets are found.
51
- def resolve!(path, options = {})
52
- uri, deps = resolve(path, options.merge(compat: false))
46
+ def resolve!(path, **kargs)
47
+ uri, deps = resolve(path, **kargs)
53
48
 
54
49
  unless uri
55
- message = "couldn't find file '#{path}'"
50
+ message = +"couldn't find file '#{path}'"
56
51
 
57
- if relative_path?(path) && options[:base_path]
58
- load_path, _ = paths_split(config[:paths], options[:base_path])
52
+ if relative_path?(path) && kargs[:base_path]
53
+ load_path, _ = paths_split(config[:paths], kargs[:base_path])
59
54
  message << " under '#{load_path}'"
60
55
  end
61
56
 
62
- message << " with type '#{options[:accept]}'" if options[:accept]
63
- message << "\nChecked in these paths: \n #{ config[:paths].join("\n ") }"
57
+ message << " with type '#{kargs[:accept]}'" if kargs[:accept]
58
+
59
+ load_paths = kargs[:load_paths] || config[:paths]
60
+ message << "\nChecked in these paths: \n #{ load_paths.join("\n ") }"
64
61
 
65
62
  raise FileNotFound, message
66
63
  end
@@ -69,143 +66,230 @@ module Sprockets
69
66
  end
70
67
 
71
68
  protected
69
+
70
+ # Internal: Finds an asset given a URI
71
+ #
72
+ # uri - String. Contains file:// scheme, absolute path to
73
+ # file.
74
+ # e.g. "file:///Users/schneems/sprockets/test/fixtures/default/gallery.js?type=application/javascript"
75
+ #
76
+ # Returns Array. Contains a String uri and Set of dependencies
72
77
  def resolve_asset_uri(uri)
73
- filename, _ = parse_asset_uri(uri)
74
- return uri, Set.new([build_file_digest_uri(filename)])
78
+ filename, _ = URIUtils.parse_asset_uri(uri)
79
+ return uri, Set.new( [URIUtils.build_file_digest_uri(filename)] )
75
80
  end
76
81
 
82
+ # Internal: Finds a file in a set of given paths
83
+ #
84
+ # paths - Array of Strings.
85
+ # filename - String containing absolute path to a file including extension.
86
+ # e.g. "/Users/schneems/sprockets/test/fixtures/asset/application.js"
87
+ # accept - String. A Quality value incoded set of
88
+ # mime types that we are looking for. Can be nil.
89
+ # e.g. "application/javascript" or "text/css, */*"
90
+ #
91
+ # Returns Array. Filename, type, path_pipeline, deps, index_alias
77
92
  def resolve_absolute_path(paths, filename, accept)
78
93
  deps = Set.new
79
94
  filename = File.expand_path(filename)
80
95
 
81
96
  # Ensure path is under load paths
82
- return nil, nil, deps unless paths_split(paths, filename)
97
+ return nil, nil, deps unless PathUtils.paths_split(paths, filename)
83
98
 
84
- _, mime_type, _, _ = parse_path_extnames(filename)
99
+ _, mime_type = PathUtils.match_path_extname(filename, config[:mime_exts])
85
100
  type = resolve_transform_type(mime_type, accept)
86
101
  return nil, nil, deps if accept && !type
87
102
 
88
103
  return nil, nil, deps unless file?(filename)
89
104
 
90
- deps << build_file_digest_uri(filename)
105
+ deps << URIUtils.build_file_digest_uri(filename)
91
106
  return filename, type, deps
92
107
  end
93
108
 
109
+ # Internal: Finds a relative file in a set of given paths
110
+ #
111
+ # paths - Array of Strings.
112
+ # path - String. A relative filename with or without extension
113
+ # e.g. "./jquery" or "../foo.js"
114
+ # dirname - String. Base path where we start looking for the given file.
115
+ # accept - String. A Quality value incoded set of
116
+ # mime types that we are looking for. Can be nil.
117
+ # e.g. "application/javascript" or "text/css, */*"
118
+ #
119
+ # Returns Array. Filename, type, path_pipeline, deps, index_alias
94
120
  def resolve_relative_path(paths, path, dirname, accept)
95
121
  filename = File.expand_path(path, dirname)
96
- load_path, _ = paths_split(paths, dirname)
97
- if load_path && logical_path = split_subpath(load_path, filename)
122
+ load_path, _ = PathUtils.paths_split(paths, dirname)
123
+ if load_path && logical_path = PathUtils.split_subpath(load_path, filename)
98
124
  resolve_logical_path([load_path], logical_path, accept)
99
125
  else
100
- return nil, nil, Set.new
126
+ return nil, nil, nil, Set.new
101
127
  end
102
128
  end
103
129
 
130
+ # Internal: Finds a file in a set of given paths
131
+ #
132
+ # paths - Array of Strings.
133
+ # logical_path - String. A filename with extension
134
+ # e.g. "coffee/foo.js" or "foo.js"
135
+ # accept - String. A Quality value incoded set of
136
+ # mime types that we are looking for. Can be nil.
137
+ # e.g. "application/javascript" or "text/css, */*"
138
+ #
139
+ # Finds a file on the given paths.
140
+ #
141
+ # Returns Array. Filename, type, path_pipeline, deps, index_alias
104
142
  def resolve_logical_path(paths, logical_path, accept)
105
- logical_name, mime_type, _, pipeline = parse_path_extnames(logical_path)
143
+ extname, mime_type = PathUtils.match_path_extname(logical_path, config[:mime_exts])
144
+ logical_name = logical_path.chomp(extname)
145
+
146
+ extname, pipeline = PathUtils.match_path_extname(logical_name, config[:pipeline_exts])
147
+ logical_name = logical_name.chomp(extname)
148
+
106
149
  parsed_accept = parse_accept_options(mime_type, accept)
107
150
  transformed_accepts = expand_transform_accepts(parsed_accept)
108
- filename, mime_type, deps = resolve_under_paths(paths, logical_name, transformed_accepts)
151
+
152
+ filename, mime_type, deps, index_alias = resolve_under_paths(paths, logical_name, transformed_accepts)
109
153
 
110
154
  if filename
111
155
  deps << build_file_digest_uri(filename)
112
156
  type = resolve_transform_type(mime_type, parsed_accept)
113
- return filename, type, pipeline, deps
157
+ return filename, type, pipeline, deps, index_alias
114
158
  else
115
159
  return nil, nil, nil, deps
116
160
  end
117
161
  end
118
162
 
163
+ # Internal: Finds a file in a set of given paths
164
+ #
165
+ # paths - Array of Strings.
166
+ # logical_name - String. A filename without extension
167
+ # e.g. "application" or "coffee/foo"
168
+ # accepts - Array of array containing mime/version pairs
169
+ # e.g. [["application/javascript", 1.0]]
170
+ #
171
+ # Finds a file with the same name as `logical_name` or "index" inside
172
+ # of the `logical_name` directory that matches a valid mime-type/version from
173
+ # `accepts`.
174
+ #
175
+ # Returns Array. Filename, type, dependencies, and index_alias
119
176
  def resolve_under_paths(paths, logical_name, accepts)
120
- all_deps = Set.new
121
- return nil, nil, all_deps if accepts.empty?
177
+ deps = Set.new
178
+ return nil, nil, deps if accepts.empty?
179
+
180
+ # TODO: Allow new path resolves to be registered
181
+ @resolvers ||= [
182
+ method(:resolve_main_under_path),
183
+ method(:resolve_alts_under_path),
184
+ method(:resolve_index_under_path)
185
+ ]
186
+ mime_exts = config[:mime_exts]
122
187
 
123
- logical_basename = File.basename(logical_name)
124
188
  paths.each do |load_path|
125
- candidates, deps = path_matches(load_path, logical_name, logical_basename)
126
- all_deps.merge(deps)
127
- candidate = find_best_q_match(accepts, candidates) do |c, matcher|
128
- match_mime_type?(c[1] || "application/octet-stream", matcher)
189
+ candidates = []
190
+ @resolvers.each do |fn|
191
+ result = fn.call(load_path, logical_name, mime_exts)
192
+ candidates.concat(result[0])
193
+ deps.merge(result[1])
129
194
  end
130
- return candidate + [all_deps] if candidate
131
- end
132
195
 
133
- return nil, nil, all_deps
134
- end
135
-
136
- def parse_accept_options(mime_type, types)
137
- accepts = []
138
- accepts += parse_q_values(types) if types
139
-
140
- if mime_type
141
- if accepts.empty? || accepts.any? { |accept, _| match_mime_type?(mime_type, accept) }
142
- accepts = [[mime_type, 1.0]]
143
- else
144
- return []
196
+ candidate = HTTPUtils.find_best_q_match(accepts, candidates) do |c, matcher|
197
+ match_mime_type?(c[:type] || "application/octet-stream", matcher)
145
198
  end
199
+ return candidate[:filename], candidate[:type], deps, candidate[:index_alias] if candidate
146
200
  end
147
201
 
148
- if accepts.empty?
149
- accepts << ['*/*', 1.0]
150
- end
151
-
152
- accepts
202
+ return nil, nil, deps
153
203
  end
154
204
 
155
- def path_matches(load_path, logical_name, logical_basename)
205
+ # Internal: Finds candidate files on a given path
206
+ #
207
+ # load_path - String. An absolute path to a directory
208
+ # logical_name - String. A filename without extension
209
+ # e.g. "application" or "coffee/foo"
210
+ # mime_exts - Hash of file extensions and their mime types
211
+ # e.g. {".xml.builder"=>"application/xml+builder"}
212
+ #
213
+ # Finds files that match a given `logical_name` with an acceptable
214
+ # mime type that is included in `mime_exts` on the `load_path`.
215
+ #
216
+ # Returns Array. First element is an Array of hashes or empty, second is a String
217
+ def resolve_main_under_path(load_path, logical_name, mime_exts)
156
218
  dirname = File.dirname(File.join(load_path, logical_name))
157
- candidates = dirname_matches(dirname, logical_basename)
158
- deps = file_digest_dependency_set(dirname)
159
-
160
- result = resolve_alternates(load_path, logical_name)
161
- result[0].each do |fn|
162
- candidates << [fn, parse_path_extnames(fn)[1]]
219
+ candidates = self.find_matching_path_for_extensions(dirname, File.basename(logical_name), mime_exts)
220
+ candidates.map! do |c|
221
+ { filename: c[0], type: c[1] }
163
222
  end
164
- deps.merge(result[1])
223
+ return candidates, [ URIUtils.build_file_digest_uri(dirname) ]
224
+ end
225
+
165
226
 
227
+ # Internal: Finds candidate index files in a given path
228
+ #
229
+ # load_path - String. An absolute path to a directory
230
+ # logical_name - String. A filename without extension
231
+ # e.g. "application" or "coffee/foo"
232
+ # mime_exts - Hash of file extensions and their mime types
233
+ # e.g. {".xml.builder"=>"application/xml+builder"}
234
+ #
235
+ # Looking in the given `load_path` this method will find all files under the `logical_name` directory
236
+ # that are named `index` and have a matching mime type in `mime_exts`.
237
+ #
238
+ # Returns Array. First element is an Array of hashes or empty, second is a String
239
+ def resolve_index_under_path(load_path, logical_name, mime_exts)
166
240
  dirname = File.join(load_path, logical_name)
167
- if directory? dirname
168
- result = dirname_matches(dirname, "index")
169
- candidates.concat(result)
241
+
242
+ if self.directory?(dirname)
243
+ candidates = self.find_matching_path_for_extensions(dirname, "index".freeze, mime_exts)
244
+ else
245
+ candidates = []
170
246
  end
171
247
 
172
- deps.merge(file_digest_dependency_set(dirname))
248
+ candidates.map! do |c|
249
+ { filename: c[0],
250
+ type: c[1],
251
+ index_alias: compress_from_root(c[0].sub(/\/index(\.[^\/]+)$/, '\1')) }
252
+ end
173
253
 
174
- return candidates.select { |fn, _| file?(fn) }, deps
254
+ return candidates, [ URIUtils.build_file_digest_uri(dirname) ]
175
255
  end
176
256
 
177
- def dirname_matches(dirname, basename)
178
- candidates = []
179
- entries = self.entries(dirname)
180
- entries.each do |entry|
181
- next unless File.basename(entry).start_with?(basename)
182
- name, type, _, _ = parse_path_extnames(entry)
183
- if basename == name
184
- candidates << [File.join(dirname, entry), type]
185
- end
257
+ def resolve_alts_under_path(load_path, logical_name, mime_exts)
258
+ filenames, deps = self.resolve_alternates(load_path, logical_name)
259
+ filenames.map! do |fn|
260
+ _, mime_type = PathUtils.match_path_extname(fn, mime_exts)
261
+ { filename: fn, type: mime_type }
186
262
  end
187
- candidates
263
+ return filenames, deps
188
264
  end
189
265
 
190
- def resolve_alternates(load_path, logical_name)
191
- return [], Set.new
192
- end
193
-
194
- # Internal: Returns the name, mime type and `Array` of engine extensions.
266
+ # Internal: Converts mimetype into accept Array
195
267
  #
196
- # "foo.js.coffee.erb"
197
- # # => ["foo", "application/javascript", [".coffee", ".erb"]]
268
+ # - mime_type - String, optional. e.g. "text/html"
269
+ # - explicit_type - String, optional. e.g. "application/javascript"
198
270
  #
199
- def parse_path_extnames(path)
200
- engines = []
201
- extname, value = match_path_extname(path, extname_map)
202
-
203
- if extname
204
- path = path.chomp(extname)
205
- type, engines, pipeline = value.values_at(:type, :engines, :pipeline)
271
+ # When called with an explicit_type and a mime_type, only a mime_type
272
+ # that matches the given explicit_type will be accepted.
273
+ #
274
+ # Returns Array of Array
275
+ #
276
+ # [["application/javascript", 1.0]]
277
+ # [["*/*", 1.0]]
278
+ # []
279
+ def parse_accept_options(mime_type, explicit_type)
280
+ if mime_type
281
+ return [[mime_type, 1.0]] if explicit_type.nil?
282
+ return [[mime_type, 1.0]] if HTTPUtils.parse_q_values(explicit_type).any? { |accept, _| HTTPUtils.match_mime_type?(mime_type, accept) }
283
+ return []
206
284
  end
207
285
 
208
- return path, type, engines, pipeline
286
+ accepts = HTTPUtils.parse_q_values(explicit_type)
287
+ accepts << ['*/*'.freeze, 1.0] if accepts.empty?
288
+ return accepts
289
+ end
290
+
291
+ def resolve_alternates(load_path, logical_name)
292
+ return [], Set.new
209
293
  end
210
294
  end
211
295
  end