sprockets 3.0.1 → 3.7.5

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 (52) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +308 -0
  3. data/README.md +49 -187
  4. data/bin/sprockets +1 -0
  5. data/lib/sprockets/asset.rb +3 -2
  6. data/lib/sprockets/base.rb +13 -2
  7. data/lib/sprockets/bundle.rb +5 -1
  8. data/lib/sprockets/cache/file_store.rb +7 -4
  9. data/lib/sprockets/cache.rb +6 -4
  10. data/lib/sprockets/closure_compressor.rb +5 -11
  11. data/lib/sprockets/coffee_script_processor.rb +2 -2
  12. data/lib/sprockets/coffee_script_template.rb +12 -1
  13. data/lib/sprockets/compressing.rb +20 -0
  14. data/lib/sprockets/dependencies.rb +8 -8
  15. data/lib/sprockets/deprecation.rb +90 -0
  16. data/lib/sprockets/digest_utils.rb +81 -57
  17. data/lib/sprockets/directive_processor.rb +2 -0
  18. data/lib/sprockets/eco_processor.rb +2 -2
  19. data/lib/sprockets/eco_template.rb +12 -1
  20. data/lib/sprockets/ejs_processor.rb +2 -2
  21. data/lib/sprockets/ejs_template.rb +12 -1
  22. data/lib/sprockets/encoding_utils.rb +7 -4
  23. data/lib/sprockets/engines.rb +11 -0
  24. data/lib/sprockets/erb_processor.rb +13 -1
  25. data/lib/sprockets/erb_template.rb +6 -1
  26. data/lib/sprockets/errors.rb +0 -1
  27. data/lib/sprockets/http_utils.rb +3 -1
  28. data/lib/sprockets/legacy.rb +20 -12
  29. data/lib/sprockets/legacy_proc_processor.rb +1 -1
  30. data/lib/sprockets/legacy_tilt_processor.rb +2 -2
  31. data/lib/sprockets/loader.rb +208 -59
  32. data/lib/sprockets/manifest.rb +57 -6
  33. data/lib/sprockets/mime.rb +26 -6
  34. data/lib/sprockets/path_utils.rb +20 -15
  35. data/lib/sprockets/processing.rb +10 -0
  36. data/lib/sprockets/processor_utils.rb +77 -0
  37. data/lib/sprockets/resolve.rb +10 -7
  38. data/lib/sprockets/sass_cache_store.rb +6 -1
  39. data/lib/sprockets/sass_compressor.rb +9 -17
  40. data/lib/sprockets/sass_processor.rb +16 -9
  41. data/lib/sprockets/sass_template.rb +14 -2
  42. data/lib/sprockets/server.rb +34 -14
  43. data/lib/sprockets/uglifier_compressor.rb +6 -13
  44. data/lib/sprockets/unloaded_asset.rb +137 -0
  45. data/lib/sprockets/uri_tar.rb +98 -0
  46. data/lib/sprockets/uri_utils.rb +14 -11
  47. data/lib/sprockets/utils/gzip.rb +67 -0
  48. data/lib/sprockets/utils.rb +36 -18
  49. data/lib/sprockets/version.rb +1 -1
  50. data/lib/sprockets/yui_compressor.rb +4 -14
  51. data/lib/sprockets.rb +21 -11
  52. metadata +49 -11
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module Sprockets
2
4
  # Functional utilities for dealing with Processor functions.
3
5
  #
@@ -99,5 +101,80 @@ module Sprockets
99
101
  def processors_cache_keys(processors)
100
102
  processors.map { |processor| processor_cache_key(processor) }
101
103
  end
104
+
105
+ # Internal: Set of all "simple" value types allowed to be returned in
106
+ # processor metadata.
107
+ VALID_METADATA_VALUE_TYPES = Set.new([
108
+ String,
109
+ Symbol,
110
+ TrueClass,
111
+ FalseClass,
112
+ NilClass
113
+ ] + (0.class == Integer ? [Integer] : [Bignum, Fixnum])).freeze
114
+
115
+ # Internal: Set of all nested compound metadata types that can nest values.
116
+ VALID_METADATA_COMPOUND_TYPES = Set.new([
117
+ Array,
118
+ Hash,
119
+ Set
120
+ ]).freeze
121
+
122
+ # Internal: Hash of all "simple" value types allowed to be returned in
123
+ # processor metadata.
124
+ VALID_METADATA_VALUE_TYPES_HASH = VALID_METADATA_VALUE_TYPES.each_with_object({}) do |type, hash|
125
+ hash[type] = true
126
+ end.freeze
127
+
128
+ # Internal: Hash of all nested compound metadata types that can nest values.
129
+ VALID_METADATA_COMPOUND_TYPES_HASH = VALID_METADATA_COMPOUND_TYPES.each_with_object({}) do |type, hash|
130
+ hash[type] = true
131
+ end.freeze
132
+
133
+ # Internal: Set of all allowed metadata types.
134
+ VALID_METADATA_TYPES = (VALID_METADATA_VALUE_TYPES + VALID_METADATA_COMPOUND_TYPES).freeze
135
+
136
+ # Internal: Validate returned result of calling a processor pipeline and
137
+ # raise a friendly user error message.
138
+ #
139
+ # result - Metadata Hash returned from call_processors
140
+ #
141
+ # Returns result or raises a TypeError.
142
+ def validate_processor_result!(result)
143
+ if !result.instance_of?(Hash)
144
+ raise TypeError, "processor metadata result was expected to be a Hash, but was #{result.class}"
145
+ end
146
+
147
+ if !result[:data].instance_of?(String)
148
+ raise TypeError, "processor :data was expected to be a String, but as #{result[:data].class}"
149
+ end
150
+
151
+ result.each do |key, value|
152
+ if !key.instance_of?(Symbol)
153
+ raise TypeError, "processor metadata[#{key.inspect}] expected to be a Symbol"
154
+ 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
+ end
161
+
162
+ result
163
+ 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
102
179
  end
103
180
  end
@@ -53,7 +53,15 @@ module Sprockets
53
53
 
54
54
  unless uri
55
55
  message = "couldn't find file '#{path}'"
56
+
57
+ if relative_path?(path) && options[:base_path]
58
+ load_path, _ = paths_split(config[:paths], options[:base_path])
59
+ message << " under '#{load_path}'"
60
+ end
61
+
56
62
  message << " with type '#{options[:accept]}'" if options[:accept]
63
+ message << "\nChecked in these paths: \n #{ config[:paths].join("\n ") }"
64
+
57
65
  raise FileNotFound, message
58
66
  end
59
67
 
@@ -144,12 +152,6 @@ module Sprockets
144
152
  accepts
145
153
  end
146
154
 
147
- def normalize_logical_path(path)
148
- dirname, basename = File.split(path)
149
- path = dirname if basename == 'index'
150
- path
151
- end
152
-
153
155
  def path_matches(load_path, logical_name, logical_basename)
154
156
  dirname = File.dirname(File.join(load_path, logical_name))
155
157
  candidates = dirname_matches(dirname, logical_basename)
@@ -176,6 +178,7 @@ module Sprockets
176
178
  candidates = []
177
179
  entries = self.entries(dirname)
178
180
  entries.each do |entry|
181
+ next unless File.basename(entry).start_with?(basename)
179
182
  name, type, _, _ = parse_path_extnames(entry)
180
183
  if basename == name
181
184
  candidates << [File.join(dirname, entry), type]
@@ -199,7 +202,7 @@ module Sprockets
199
202
 
200
203
  if extname
201
204
  path = path.chomp(extname)
202
- type, engines, pipeline = value.values_at(:type, :engines, :pipeline)
205
+ type, engines, pipeline = value
203
206
  end
204
207
 
205
208
  return path, type, engines, pipeline
@@ -25,5 +25,10 @@ module Sprockets
25
25
  end
26
26
 
27
27
  # Deprecated: Use Sprockets::SassProcessor::CacheStore instead.
28
- SassCacheStore = SassProcessor::CacheStore
28
+ class SassCacheStore < SassProcessor::CacheStore
29
+ def initialize(*args)
30
+ Deprecation.new.warn "SassCacheStore is deprecated please use SassProcessor::CacheStore instead"
31
+ super
32
+ end
33
+ end
29
34
  end
@@ -1,4 +1,5 @@
1
1
  require 'sprockets/autoload'
2
+ require 'sprockets/digest_utils'
2
3
 
3
4
  module Sprockets
4
5
  # Public: Sass CSS minifier.
@@ -34,26 +35,17 @@ module Sprockets
34
35
  attr_reader :cache_key
35
36
 
36
37
  def initialize(options = {})
37
- @options = options
38
- @cache_key = [
39
- self.class.name,
40
- Autoload::Sass::VERSION,
41
- VERSION,
42
- options
43
- ].freeze
38
+ @options = {
39
+ syntax: :scss,
40
+ cache: false,
41
+ read_cache: false,
42
+ style: :compressed
43
+ }.merge(options).freeze
44
+ @cache_key = "#{self.class.name}:#{Autoload::Sass::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
44
45
  end
45
46
 
46
47
  def call(input)
47
- data = input[:data]
48
- input[:cache].fetch(@cache_key + [data]) do
49
- options = {
50
- syntax: :scss,
51
- cache: false,
52
- read_cache: false,
53
- style: :compressed
54
- }.merge(@options)
55
- Autoload::Sass::Engine.new(data, options).render
56
- end
48
+ Autoload::Sass::Engine.new(input[:data], @options).render
57
49
  end
58
50
  end
59
51
  end
@@ -39,17 +39,12 @@ module Sprockets
39
39
  # Public: Initialize template with custom options.
40
40
  #
41
41
  # options - Hash
42
- # cache_version - String custom cache version. Used to force a cache
43
- # change after code changes are made to Sass Functions.
42
+ # cache_version - String custom cache version. Used to force a cache
43
+ # change after code changes are made to Sass Functions.
44
44
  #
45
45
  def initialize(options = {}, &block)
46
46
  @cache_version = options[:cache_version]
47
- @cache_key = [
48
- self.class.name,
49
- VERSION,
50
- Autoload::Sass::VERSION,
51
- @cache_version
52
- ].freeze
47
+ @cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze
53
48
 
54
49
  @functions = Module.new do
55
50
  include Functions
@@ -64,7 +59,7 @@ module Sprockets
64
59
  options = {
65
60
  filename: input[:filename],
66
61
  syntax: self.class.syntax,
67
- cache_store: CacheStore.new(input[:cache], @cache_version),
62
+ cache_store: build_cache_store(input, @cache_version),
68
63
  load_paths: input[:environment].paths,
69
64
  sprockets: {
70
65
  context: context,
@@ -89,6 +84,18 @@ module Sprockets
89
84
  context.metadata.merge(data: css, sass_dependencies: sass_dependencies)
90
85
  end
91
86
 
87
+ # Public: Build the cache store to be used by the Sass engine.
88
+ #
89
+ # input - the input hash.
90
+ # version - the cache version.
91
+ #
92
+ # Override this method if you need to use a different cache than the
93
+ # Sprockets cache.
94
+ def build_cache_store(input, version)
95
+ CacheStore.new(input[:cache], version)
96
+ end
97
+ private :build_cache_store
98
+
92
99
  # Public: Functions injected into Sass context during Sprockets evaluation.
93
100
  #
94
101
  # This module may be extended to add global functionality to all Sprockets
@@ -2,6 +2,18 @@ require 'sprockets/sass_processor'
2
2
 
3
3
  module Sprockets
4
4
  # Deprecated
5
- SassTemplate = SassProcessor
6
- ScssTemplate = ScssProcessor
5
+ class SassTemplate < SassProcessor
6
+ def self.call(*args)
7
+ Deprecation.new.warn "SassTemplate is deprecated please use SassProcessor instead"
8
+ super
9
+ end
10
+ end
11
+
12
+ # Deprecated
13
+ class ScssTemplate < ScssProcessor
14
+ def self.call(*args)
15
+ Deprecation.new.warn "ScssTemplate is deprecated please use ScssProcessor instead"
16
+ super
17
+ end
18
+ end
7
19
  end
@@ -23,7 +23,7 @@ module Sprockets
23
23
  start_time = Time.now.to_f
24
24
  time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
25
25
 
26
- if env['REQUEST_METHOD'] != 'GET'
26
+ if !['GET', 'HEAD'].include?(env['REQUEST_METHOD'])
27
27
  return method_not_allowed_response
28
28
  end
29
29
 
@@ -39,7 +39,7 @@ module Sprockets
39
39
 
40
40
  # URLs containing a `".."` are rejected for security reasons.
41
41
  if forbidden_request?(path)
42
- return forbidden_response
42
+ return forbidden_response(env)
43
43
  end
44
44
 
45
45
  # Look up the asset.
@@ -86,10 +86,10 @@ module Sprockets
86
86
  not_modified_response(env, if_none_match)
87
87
  when :not_found
88
88
  logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
89
- not_found_response
89
+ not_found_response(env)
90
90
  when :precondition_failed
91
91
  logger.info "#{msg} 412 Precondition Failed (#{time_elapsed.call}ms)"
92
- precondition_failed_response
92
+ precondition_failed_response(env)
93
93
  end
94
94
  rescue Exception => e
95
95
  logger.error "Error compiling asset #{path}:"
@@ -115,12 +115,20 @@ module Sprockets
115
115
  #
116
116
  # http://example.org/assets/../../../etc/passwd
117
117
  #
118
- path.include?("..") || absolute_path?(path)
118
+ path.include?("..") || absolute_path?(path) || path.include?("://")
119
+ end
120
+
121
+ def head_request?(env)
122
+ env['REQUEST_METHOD'] == 'HEAD'
119
123
  end
120
124
 
121
125
  # Returns a 200 OK response tuple
122
126
  def ok_response(asset, env)
123
- [ 200, headers(env, asset, asset.length), asset ]
127
+ if head_request?(env)
128
+ [ 200, headers(env, asset, 0), [] ]
129
+ else
130
+ [ 200, headers(env, asset, asset.length), asset ]
131
+ end
124
132
  end
125
133
 
126
134
  # Returns a 304 Not Modified response tuple
@@ -129,21 +137,33 @@ module Sprockets
129
137
  end
130
138
 
131
139
  # Returns a 403 Forbidden response tuple
132
- def forbidden_response
133
- [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
140
+ def forbidden_response(env)
141
+ if head_request?(env)
142
+ [ 403, { "Content-Type" => "text/plain", "Content-Length" => "0" }, [] ]
143
+ else
144
+ [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
145
+ end
134
146
  end
135
147
 
136
148
  # Returns a 404 Not Found response tuple
137
- def not_found_response
138
- [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
149
+ def not_found_response(env)
150
+ if head_request?(env)
151
+ [ 404, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
152
+ else
153
+ [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
154
+ end
139
155
  end
140
156
 
141
157
  def method_not_allowed_response
142
158
  [ 405, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ]
143
159
  end
144
160
 
145
- def precondition_failed_response
146
- [ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
161
+ def precondition_failed_response(env)
162
+ if head_request?(env)
163
+ [ 412, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
164
+ else
165
+ [ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
166
+ end
147
167
  end
148
168
 
149
169
  # Returns a JavaScript response that re-throws a Ruby exception
@@ -231,11 +251,11 @@ module Sprockets
231
251
  # If the request url contains a fingerprint, set a long
232
252
  # expires on the response
233
253
  if path_fingerprint(env["PATH_INFO"])
234
- headers["Cache-Control"] << ", max-age=31536000"
254
+ headers["Cache-Control"] += ", max-age=31536000"
235
255
 
236
256
  # Otherwise set `must-revalidate` since the asset could be modified.
237
257
  else
238
- headers["Cache-Control"] << ", must-revalidate"
258
+ headers["Cache-Control"] += ", must-revalidate"
239
259
  headers["Vary"] = "Accept-Encoding"
240
260
  end
241
261
 
@@ -1,4 +1,5 @@
1
1
  require 'sprockets/autoload'
2
+ require 'sprockets/digest_utils'
2
3
 
3
4
  module Sprockets
4
5
  # Public: Uglifier/Uglify compressor.
@@ -40,24 +41,16 @@ module Sprockets
40
41
  options[:copyright] ||= false
41
42
  else
42
43
  # Uglifier >= 2.x
43
- options[:copyright] ||= :none
44
+ options[:comments] ||= :none
44
45
  end
45
46
 
46
- @uglifier = Autoload::Uglifier.new(options)
47
-
48
- @cache_key = [
49
- self.class.name,
50
- Autoload::Uglifier::VERSION,
51
- VERSION,
52
- options
53
- ].freeze
47
+ @options = options
48
+ @cache_key = "#{self.class.name}:#{Autoload::Uglifier::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
54
49
  end
55
50
 
56
51
  def call(input)
57
- data = input[:data]
58
- input[:cache].fetch(@cache_key + [data]) do
59
- @uglifier.compile(data)
60
- end
52
+ @uglifier ||= Autoload::Uglifier.new(@options)
53
+ @uglifier.compile(input[:data])
61
54
  end
62
55
  end
63
56
  end
@@ -0,0 +1,137 @@
1
+ require 'sprockets/uri_utils'
2
+ require 'sprockets/uri_tar'
3
+
4
+ module Sprockets
5
+ # Internal: Used to parse and store the URI to an unloaded asset
6
+ # Generates keys used to store and retrieve items from cache
7
+ class UnloadedAsset
8
+
9
+ # Internal: Initialize object for generating cache keys
10
+ #
11
+ # uri - A String containing complete URI to a file including scheme
12
+ # and full path such as
13
+ # "file:///Path/app/assets/js/app.js?type=application/javascript"
14
+ # env - The current "environment" that assets are being loaded into.
15
+ # We need it so we know where the +root+ (directory where sprockets
16
+ # is being invoked). We also need for the `file_digest` method,
17
+ # since, for some strange reason, memoization is provided by
18
+ # overriding methods such as `stat` in the `PathUtils` module.
19
+ #
20
+ # Returns UnloadedAsset.
21
+ def initialize(uri, env)
22
+ @uri = uri.to_s
23
+ @env = env
24
+ @compressed_path = URITar.new(uri, env).compressed_path
25
+ @params = nil # lazy loaded
26
+ @filename = nil # lazy loaded
27
+ end
28
+ attr_reader :compressed_path, :uri
29
+
30
+ # Internal: Full file path without schema
31
+ #
32
+ # This returns a string containing the full path to the asset without the schema.
33
+ # Information is loaded lazilly since we want `UnloadedAsset.new(dep, self).relative_path`
34
+ # to be fast. Calling this method the first time allocates an array and a hash.
35
+ #
36
+ # Example
37
+ #
38
+ # If the URI is `file:///Full/path/app/assets/javascripts/application.js"` then the
39
+ # filename would be `"/Full/path/app/assets/javascripts/application.js"`
40
+ #
41
+ # Returns a String.
42
+ def filename
43
+ unless @filename
44
+ load_file_params
45
+ end
46
+ @filename
47
+ end
48
+
49
+ # Internal: Hash of param values
50
+ #
51
+ # This information is generated and used internally by sprockets.
52
+ # Known keys include `:type` which store the asset's mime-type, `:id` which is a fully resolved
53
+ # digest for the asset (includes dependency digest as opposed to a digest of only file contents)
54
+ # and `:pipeline`. Hash may be empty.
55
+ #
56
+ # Example
57
+ #
58
+ # If the URI is `file:///Full/path/app/assets/javascripts/application.js"type=application/javascript`
59
+ # Then the params would be `{type: "application/javascript"}`
60
+ #
61
+ # Returns a Hash.
62
+ def params
63
+ unless @params
64
+ load_file_params
65
+ end
66
+ @params
67
+ end
68
+
69
+ # Internal: Key of asset
70
+ #
71
+ # Used to retrieve an asset from the cache based on "compressed" path to asset.
72
+ # A "compressed" path can either be relative to the root of the project or an
73
+ # absolute path.
74
+ #
75
+ # Returns a String.
76
+ def asset_key
77
+ "asset-uri:#{compressed_path}"
78
+ end
79
+
80
+ # Public: Dependency History key
81
+ #
82
+ # Used to retrieve an array of "histories" each of which contain a set of stored dependencies
83
+ # for a given asset path and filename digest.
84
+ #
85
+ # A dependency can refer to either an asset i.e. index.js
86
+ # may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect
87
+ # compilation, such as the VERSION of sprockets (i.e. the environment) and what "processors"
88
+ # are used.
89
+ #
90
+ # For example a history array with one Set of dependencies may look like:
91
+ #
92
+ # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
93
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
94
+ # "processors:type=text/css&file_type=text/css&pipeline=self",
95
+ # "file-digest:///Full/path/app/assets/stylesheets"]]
96
+ #
97
+ # This method of asset lookup is used to ensure that none of the dependencies have been modified
98
+ # since last lookup. If one of them has, the key will be different and a new entry must be stored.
99
+ #
100
+ # URI depndencies are later converted to "compressed" paths
101
+ #
102
+ # Returns a String.
103
+ def dependency_history_key
104
+ "asset-uri-cache-dependencies:#{compressed_path}:#{ @env.file_digest(filename) }"
105
+ end
106
+
107
+ # Internal: Digest key
108
+ #
109
+ # Used to retrieve a string containing the "compressed" path to an asset based on
110
+ # a digest. The digest is generated from dependencies stored via information stored in
111
+ # the `dependency_history_key` after each of the "dependencies" is "resolved" for example
112
+ # "environment-version" may be resolved to "environment-1.0-3.2.0" for version "3.2.0" of sprockets
113
+ #
114
+ # Returns a String.
115
+ def digest_key(digest)
116
+ "asset-uri-digest:#{compressed_path}:#{digest}"
117
+ end
118
+
119
+ # Internal: File digest key
120
+ #
121
+ # The digest for a given file won't change if the path and the stat time hasn't changed
122
+ # We can save time by not re-computing this information and storing it in the cache
123
+ #
124
+ # Returns a String.
125
+ def file_digest_key(stat)
126
+ "file_digest:#{compressed_path}:#{stat}"
127
+ end
128
+
129
+ private
130
+ # Internal: Parses uri into filename and params hash
131
+ #
132
+ # Returns Array with filename and params hash
133
+ def load_file_params
134
+ @filename, @params = URIUtils.parse_asset_uri(uri)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,98 @@
1
+ require 'sprockets/path_utils'
2
+
3
+ module Sprockets
4
+ # Internal: used to "expand" and "compress" values for storage
5
+ class URITar
6
+ attr_reader :scheme, :root, :path
7
+
8
+ # Internal: Initialize object for compression or expansion
9
+ #
10
+ # uri - A String containing URI that may or may not contain the scheme
11
+ # env - The current "environment" that assets are being loaded into.
12
+ def initialize(uri, env)
13
+ @root = env.root
14
+ @env = env
15
+ uri = uri.to_s
16
+ if uri.include?("://".freeze)
17
+ @scheme, _, @path = uri.partition("://".freeze)
18
+ @scheme << "://".freeze
19
+ else
20
+ @scheme = "".freeze
21
+ @path = uri
22
+ end
23
+ end
24
+
25
+ # Internal: Converts full uri to a "compressed" uri
26
+ #
27
+ # If a uri is inside of an environment's root it will
28
+ # be shortened to be a relative path.
29
+ #
30
+ # If a uri is outside of the environment's root the original
31
+ # uri will be returned.
32
+ #
33
+ # Returns String
34
+ def compress
35
+ scheme + compressed_path
36
+ end
37
+
38
+ # Internal: Tells us if we are using an absolute path
39
+ #
40
+ # Nix* systems start with a `/` like /Users/schneems.
41
+ # Windows systems start with a drive letter than colon and slash
42
+ # like C:/Schneems.
43
+ def absolute_path?
44
+ PathUtils.absolute_path?(path)
45
+ end
46
+
47
+ # Internal: Convert a "compressed" uri to an absolute path
48
+ #
49
+ # If a uri is inside of the environment's root it will not
50
+ # start with a slash for example:
51
+ #
52
+ # file://this/is/a/relative/path
53
+ #
54
+ # If a uri is outside the root, it will start with a slash:
55
+ #
56
+ # file:///This/is/an/absolute/path
57
+ #
58
+ # Returns String
59
+ def expand
60
+ if absolute_path?
61
+ # Stored path was absolute, don't add root
62
+ scheme + path
63
+ else
64
+ if scheme.empty?
65
+ File.join(root, path)
66
+ else
67
+ # We always want to return an absolute uri,
68
+ # make sure the path starts with a slash.
69
+ scheme + File.join("/".freeze, root, path)
70
+ end
71
+ end
72
+ end
73
+
74
+ # Internal: Returns "compressed" path
75
+ #
76
+ # If the input uri is relative to the environment root
77
+ # it will return a path relative to the environment root.
78
+ # Otherwise an absolute path will be returned.
79
+ #
80
+ # Only path information is returned, and not scheme.
81
+ #
82
+ # Returns String
83
+ def compressed_path
84
+ # windows
85
+ if !@root.start_with?("/".freeze) && path.start_with?("/".freeze)
86
+ consistent_root = "/".freeze + @root
87
+ else
88
+ consistent_root = @root
89
+ end
90
+
91
+ if compressed_path = PathUtils.split_subpath(consistent_root, path)
92
+ compressed_path
93
+ else
94
+ path
95
+ end
96
+ end
97
+ end
98
+ end