sassc-embedded 1.63.0 → 1.78.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -23
  3. data/lib/sassc/embedded/version.rb +1 -1
  4. data/lib/sassc/embedded.rb +266 -188
  5. data/vendor/github.com/sass/sassc-ruby/LICENSE.txt +22 -0
  6. data/vendor/github.com/sass/sassc-ruby/lib/sassc/dependency.rb +17 -0
  7. data/vendor/github.com/sass/sassc-ruby/lib/sassc/engine.rb +141 -0
  8. data/vendor/github.com/sass/sassc-ruby/lib/sassc/error.rb +37 -0
  9. data/vendor/github.com/sass/sassc-ruby/lib/sassc/functions_handler.rb +73 -0
  10. data/vendor/github.com/sass/sassc-ruby/lib/sassc/import_handler.rb +50 -0
  11. data/vendor/github.com/sass/sassc-ruby/lib/sassc/importer.rb +31 -0
  12. data/vendor/github.com/sass/sassc-ruby/lib/sassc/native/native_context_api.rb +147 -0
  13. data/vendor/github.com/sass/sassc-ruby/lib/sassc/native/native_functions_api.rb +159 -0
  14. data/vendor/github.com/sass/sassc-ruby/lib/sassc/native/sass2scss_api.rb +10 -0
  15. data/vendor/github.com/sass/sassc-ruby/lib/sassc/native/sass_input_style.rb +13 -0
  16. data/vendor/github.com/sass/sassc-ruby/lib/sassc/native/sass_output_style.rb +12 -0
  17. data/vendor/github.com/sass/sassc-ruby/lib/sassc/native/sass_value.rb +97 -0
  18. data/vendor/github.com/sass/sassc-ruby/lib/sassc/native/string_list.rb +10 -0
  19. data/vendor/github.com/sass/sassc-ruby/lib/sassc/native.rb +64 -0
  20. data/vendor/github.com/sass/sassc-ruby/lib/sassc/sass_2_scss.rb +9 -0
  21. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/functions.rb +8 -0
  22. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value/bool.rb +32 -0
  23. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value/color.rb +95 -0
  24. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value/list.rb +136 -0
  25. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value/map.rb +69 -0
  26. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value/number.rb +389 -0
  27. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value/string.rb +96 -0
  28. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value.rb +137 -0
  29. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value_conversion/base.rb +13 -0
  30. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value_conversion/bool.rb +13 -0
  31. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value_conversion/color.rb +18 -0
  32. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value_conversion/list.rb +25 -0
  33. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value_conversion/map.rb +21 -0
  34. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value_conversion/number.rb +13 -0
  35. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value_conversion/string.rb +17 -0
  36. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script/value_conversion.rb +69 -0
  37. data/vendor/github.com/sass/sassc-ruby/lib/sassc/script.rb +17 -0
  38. data/vendor/github.com/sass/sassc-ruby/lib/sassc/util/normalized_map.rb +117 -0
  39. data/vendor/github.com/sass/sassc-ruby/lib/sassc/util.rb +231 -0
  40. data/vendor/github.com/sass/sassc-ruby/lib/sassc/version.rb +5 -0
  41. data/vendor/github.com/sass/sassc-ruby/lib/sassc.rb +64 -0
  42. metadata +49 -23
@@ -3,7 +3,6 @@
3
3
  require 'sassc'
4
4
  require 'sass-embedded'
5
5
 
6
- require 'base64'
7
6
  require 'json'
8
7
  require 'uri'
9
8
 
@@ -11,14 +10,16 @@ require_relative 'embedded/version'
11
10
 
12
11
  module SassC
13
12
  class Engine
13
+ remove_method(:render) if public_method_defined?(:render, false)
14
+
14
15
  def render
15
16
  return @template.dup if @template.empty?
16
17
 
17
18
  result = ::Sass.compile_string(
18
19
  @template,
19
- importer: import_handler.setup(nil),
20
- load_paths: load_paths,
21
- syntax: syntax,
20
+ importer: (NoopImporter unless @options[:importer].nil?),
21
+ load_paths:,
22
+ syntax:,
22
23
  url: file_url,
23
24
 
24
25
  charset: @options.fetch(:charset, true),
@@ -27,28 +28,28 @@ module SassC
27
28
  style: output_style,
28
29
 
29
30
  functions: functions_handler.setup(nil, functions: @functions),
30
- importers: @options.fetch(:importers, []),
31
+ importers: import_handler.setup(nil).concat(@options.fetch(:importers, [])),
31
32
 
32
33
  alert_ascii: @options.fetch(:alert_ascii, false),
33
34
  alert_color: @options.fetch(:alert_color, nil),
34
- logger: @options.fetch(:logger, nil),
35
+ fatal_deprecations: @options.fetch(:fatal_deprecations, []),
36
+ future_deprecations: @options.fetch(:future_deprecations, []),
37
+ logger: quiet? ? ::Sass::Logger.silent : @options.fetch(:logger, nil),
35
38
  quiet_deps: @options.fetch(:quiet_deps, false),
39
+ silence_deprecations: @options.fetch(:silence_deprecations, []),
36
40
  verbose: @options.fetch(:verbose, false)
37
41
  )
38
42
 
39
43
  @loaded_urls = result.loaded_urls
40
44
  @source_map = result.source_map
41
45
 
42
- return if quiet?
43
-
44
46
  css = result.css
45
47
  css += "\n" unless css.empty?
46
48
  unless @source_map.nil? || omit_source_map_url?
47
- url = URL.parse(output_url || file_url)
48
49
  source_mapping_url = if source_map_embed?
49
- "data:application/json;base64,#{Base64.strict_encode64(@source_map)}"
50
+ "data:application/json;base64,#{[@source_map].pack('m0')}"
50
51
  else
51
- URL.parse(source_map_file_url).route_from(url).to_s
52
+ Uri.file_urls_to_relative_url(source_map_file_url, file_url)
52
53
  end
53
54
  css += "\n/*# sourceMappingURL=#{source_mapping_url} */"
54
55
  end
@@ -59,27 +60,30 @@ module SassC
59
60
  line = e.span&.start&.line
60
61
  line += 1 unless line.nil?
61
62
  url = e.span&.url
62
- path = (URL.parse(url).route_from(URL.path_to_file_url("#{Dir.pwd}/")) if url&.start_with?(Protocol::FILE))
63
- raise SyntaxError.new(e.full_message, filename: path, line: line)
63
+ path = (Uri.file_urls_to_relative_path(url, Uri.path_to_file_url("#{Dir.pwd}/")) if url&.start_with?('file:'))
64
+ raise SyntaxError.new(e.full_message, filename: path, line:)
64
65
  end
65
66
 
67
+ remove_method(:dependencies) if public_method_defined?(:dependencies, false)
68
+
66
69
  def dependencies
67
70
  raise NotRenderedError unless @loaded_urls
68
71
 
69
- Dependency.from_filenames(@loaded_urls
70
- .filter { |url| url.start_with?(Protocol::FILE) && url != file_url }
71
- .map { |url| URL.file_url_to_path(url) })
72
+ Dependency.from_filenames(@loaded_urls.filter_map do |url|
73
+ Uri.file_url_to_path(url) if url.start_with?('file:') && !url.include?('?') && url != file_url
74
+ end)
72
75
  end
73
76
 
77
+ remove_method(:source_map) if public_method_defined?(:source_map, false)
78
+
74
79
  def source_map
75
80
  raise NotRenderedError unless @source_map
76
81
 
77
- url = URL.parse(source_map_file_url || file_url)
82
+ url = Uri.parse(source_map_file_url || file_url)
78
83
  data = JSON.parse(@source_map)
79
- data['file'] = URL.parse(output_url).route_from(url).to_s if output_url
80
84
  data['sources'].map! do |source|
81
- if source.start_with?(Protocol::FILE)
82
- URL.parse(source).route_from(url).to_s
85
+ if source.start_with?('file:')
86
+ Uri.file_urls_to_relative_url(source, url)
83
87
  else
84
88
  source
85
89
  end
@@ -91,35 +95,29 @@ module SassC
91
95
  private
92
96
 
93
97
  def file_url
94
- @file_url ||= URL.path_to_file_url(File.absolute_path(filename || 'stdin'))
95
- end
96
-
97
- def output_path
98
- @output_path ||= @options.fetch(:output_path) do
99
- "#{filename.delete_suffix(File.extname(filename))}.css" if filename
100
- end
101
- end
102
-
103
- def output_url
104
- @output_url ||= (URL.path_to_file_url(File.absolute_path(output_path)) if output_path)
98
+ @file_url ||= Uri.path_to_file_url(File.absolute_path(filename || 'stdin'))
105
99
  end
106
100
 
107
101
  def source_map_file_url
108
- @source_map_file_url ||= (URL.path_to_file_url(File.absolute_path(source_map_file)) if source_map_file)
102
+ @source_map_file_url ||= if source_map_file
103
+ Uri.path_to_file_url(File.absolute_path(source_map_file))
104
+ .gsub('%3F', '?') # https://github.com/sass-contrib/sassc-embedded-shim-ruby/pull/69
105
+ end
109
106
  end
110
107
 
108
+ remove_method(:output_style) if private_method_defined?(:output_style, false)
109
+
111
110
  def output_style
112
111
  @output_style ||= begin
113
- style = @options.fetch(:style, :sass_style_nested).to_s
114
- style = "sass_style_#{style}" unless style.start_with?('sass_style_')
115
- raise InvalidStyleError unless OUTPUT_STYLES.include?(style.to_sym)
112
+ style = @options.fetch(:style, :sass_style_nested).to_s.delete_prefix('sass_style_').to_sym
116
113
 
117
- style = style.delete_prefix('sass_style_').to_sym
118
114
  case style
119
- when :nested, :compact
115
+ when :nested, :compact, :expanded
120
116
  :expanded
117
+ when :compressed
118
+ :compressed
121
119
  else
122
- style
120
+ raise InvalidStyleError
123
121
  end
124
122
  end
125
123
  end
@@ -130,16 +128,16 @@ module SassC
130
128
  syntax
131
129
  end
132
130
 
131
+ remove_method(:load_paths) if private_method_defined?(:load_paths, false)
132
+
133
133
  def load_paths
134
- @load_paths ||= if @options[:importer].nil?
135
- (@options[:load_paths] || []) + SassC.load_paths
136
- else
137
- []
138
- end
134
+ @load_paths ||= (@options[:load_paths] || []) + SassC.load_paths
139
135
  end
140
136
  end
141
137
 
142
138
  class FunctionsHandler
139
+ remove_method(:setup) if public_method_defined?(:setup, false)
140
+
143
141
  def setup(_native_options, functions: Script::Functions)
144
142
  @callbacks = {}
145
143
 
@@ -150,7 +148,7 @@ module SassC
150
148
  end.new
151
149
  functions_wrapper.options = @options
152
150
 
153
- Script.custom_functions(functions: functions).each do |custom_function|
151
+ Script.custom_functions(functions:).each do |custom_function|
154
152
  callback = lambda do |native_argument_list|
155
153
  function_arguments = arguments_from_native_list(native_argument_list)
156
154
  begin
@@ -164,7 +162,7 @@ module SassC
164
162
  raise e
165
163
  end
166
164
 
167
- @callbacks[Script.formatted_function_name(custom_function, functions: functions)] = callback
165
+ @callbacks[Script.formatted_function_name(custom_function, functions:)] = callback
168
166
  end
169
167
 
170
168
  @callbacks
@@ -172,40 +170,66 @@ module SassC
172
170
 
173
171
  private
174
172
 
173
+ remove_method(:arguments_from_native_list) if private_method_defined?(:arguments_from_native_list, false)
174
+
175
175
  def arguments_from_native_list(native_argument_list)
176
176
  native_argument_list.filter_map do |native_value|
177
177
  Script::ValueConversion.from_native(native_value, @options)
178
178
  end
179
179
  end
180
+ end
180
181
 
181
- begin
182
- begin
183
- raise RuntimeError
184
- rescue StandardError
185
- raise ::Sass::ScriptError
186
- end
187
- rescue StandardError => e
188
- unless e.full_message.include?(e.cause.full_message)
189
- ::Sass::ScriptError.class_eval do
190
- def full_message(...)
191
- full_message = super(...)
192
- if cause
193
- "#{full_message}\n#{cause.full_message(...)}"
194
- else
195
- full_message
196
- end
197
- end
198
- end
199
- end
200
- end
182
+ module NoopImporter
183
+ module_function
184
+
185
+ def canonicalize(...); end
186
+
187
+ def load(...); end
201
188
  end
202
189
 
190
+ private_constant :NoopImporter
191
+
203
192
  class ImportHandler
193
+ remove_method(:setup) if public_method_defined?(:setup, false)
194
+
204
195
  def setup(_native_options)
205
- Importer.new(@importer) if @importer
196
+ if @importer
197
+ import_cache = ImportCache.new(@importer)
198
+ [Importer.new(import_cache), FileImporter.new(import_cache)]
199
+ else
200
+ []
201
+ end
206
202
  end
207
203
 
204
+ class Importer
205
+ def initialize(import_cache)
206
+ @import_cache = import_cache
207
+ end
208
+
209
+ def canonicalize(...)
210
+ @import_cache.canonicalize(...)
211
+ end
212
+
213
+ def load(...)
214
+ @import_cache.load(...)
215
+ end
216
+ end
217
+
218
+ private_constant :Importer
219
+
208
220
  class FileImporter
221
+ def initialize(import_cache)
222
+ @import_cache = import_cache
223
+ end
224
+
225
+ def find_file_url(...)
226
+ @import_cache.find_file_url(...)
227
+ end
228
+ end
229
+
230
+ private_constant :FileImporter
231
+
232
+ module FileSystemImporter
209
233
  class << self
210
234
  def resolve_path(path, from_import)
211
235
  ext = File.extname(path)
@@ -217,15 +241,6 @@ module SassC
217
241
  return exactly_one(try_path(path))
218
242
  end
219
243
 
220
- unless ext.empty?
221
- if from_import
222
- result = exactly_one(try_path("#{without_ext(path)}.import#{ext}"))
223
- return result unless result.nil?
224
- end
225
- result = exactly_one(try_path(path))
226
- return result unless result.nil?
227
- end
228
-
229
244
  if from_import
230
245
  result = exactly_one(try_path_with_ext("#{path}.import"))
231
246
  return result unless result.nil?
@@ -253,7 +268,7 @@ module SassC
253
268
  end
254
269
 
255
270
  def try_path_as_dir(path, from_import)
256
- return unless dir_exist? path
271
+ return unless dir_exist?(path)
257
272
 
258
273
  if from_import
259
274
  result = exactly_one(try_path_with_ext(File.join(path, 'index.import')))
@@ -265,7 +280,7 @@ module SassC
265
280
 
266
281
  def exactly_one(paths)
267
282
  return if paths.empty?
268
- return paths.first if paths.length == 1
283
+ return paths.first if paths.one?
269
284
 
270
285
  raise "It's not clear which file to import. Found:\n#{paths.map { |path| " #{path}" }.join("\n")}"
271
286
  end
@@ -285,79 +300,63 @@ module SassC
285
300
  end
286
301
  end
287
302
 
288
- private_constant :FileImporter
303
+ private_constant :FileSystemImporter
289
304
 
290
- class Importer
305
+ class ImportCache
291
306
  def initialize(importer)
292
307
  @importer = importer
293
-
294
- @canonical_urls = {}
295
- @id = 0
296
308
  @importer_results = {}
297
- @parent_urls = [URL.path_to_file_url(File.absolute_path(@importer.options[:filename] || 'stdin'))]
309
+ @importer_result = nil
310
+ @file_url = nil
298
311
  end
299
312
 
300
- def canonicalize(url, from_import:)
301
- if url.start_with?(Protocol::IMPORT)
302
- canonical_url = @canonical_urls.delete(url.delete_prefix(Protocol::IMPORT))
313
+ def canonicalize(url, context)
314
+ return unless context.containing_url&.start_with?('file:')
315
+
316
+ containing_url = context.containing_url
317
+
318
+ path = Uri.decode_uri_component(url)
319
+ parent_path = Uri.file_url_to_path(containing_url)
320
+ parent_dir = File.dirname(parent_path)
321
+
322
+ if containing_url.include?('?')
323
+ canonical_url = Uri.path_to_file_url(File.absolute_path(path, parent_dir))
303
324
  unless @importer_results.key?(canonical_url)
304
- canonical_url = resolve_file_url(canonical_url, @parent_urls.last, from_import)
325
+ @file_url = resolve_file_url(path, parent_dir, context.from_import)
326
+ return
305
327
  end
306
- @parent_urls.push(canonical_url)
307
- canonical_url
308
- elsif url.start_with?(Protocol::FILE)
309
- path = URL.parse(url).route_from(@parent_urls.last).to_s
310
- parent_path = URL.file_url_to_path(@parent_urls.last)
311
-
312
- imports = @importer.imports(path, parent_path)
313
- imports = [SassC::Importer::Import.new(path)] if imports.nil?
314
- imports = [imports] unless imports.is_a?(Array)
315
- imports.each do |import|
316
- import.path = File.absolute_path(import.path, File.dirname(parent_path))
328
+ else
329
+ imports = [*@importer.imports(path, parent_path)]
330
+ canonical_url = imports_to_native(imports, parent_dir, context.from_import, url, containing_url)
331
+ unless @importer_results.key?(canonical_url)
332
+ @file_url = canonical_url
333
+ return
317
334
  end
318
-
319
- canonical_url = "#{Protocol::IMPORT}#{next_id}"
320
- @importer_results[canonical_url] = imports_to_native(imports)
321
- canonical_url
322
- elsif url.start_with?(Protocol::LOADED)
323
- canonical_url = Protocol::LOADED
324
- @parent_urls.pop
325
- canonical_url
326
335
  end
336
+
337
+ @importer_result = @importer_results.delete(canonical_url)
338
+ canonical_url
327
339
  end
328
340
 
329
- def load(canonical_url)
330
- if @importer_results.key?(canonical_url)
331
- @importer_results.delete(canonical_url)
332
- elsif canonical_url.start_with?(Protocol::FILE)
333
- path = URL.file_url_to_path(canonical_url)
334
- {
335
- contents: File.read(path),
336
- syntax: syntax(path),
337
- source_map_url: canonical_url
338
- }
339
- elsif canonical_url.start_with?(Protocol::LOADED)
340
- {
341
- contents: '',
342
- syntax: :scss
343
- }
344
- end
341
+ def load(_canonical_url)
342
+ importer_result = @importer_result
343
+ @importer_result = nil
344
+ importer_result
345
345
  end
346
346
 
347
- private
347
+ def find_file_url(_url, context)
348
+ return if context.containing_url.nil? || @file_url.nil?
348
349
 
349
- def load_paths
350
- @load_paths ||= (@importer.options[:load_paths] || []) + SassC.load_paths
350
+ canonical_url = @file_url
351
+ @file_url = nil
352
+ canonical_url
351
353
  end
352
354
 
353
- def resolve_file_url(url, parent_url, from_import)
354
- path = URL.parse(url).route_from(parent_url).to_s
355
- parent_path = URL.file_url_to_path(parent_url)
356
- [File.dirname(parent_path)].concat(load_paths).each do |load_path|
357
- resolved = FileImporter.resolve_path(File.absolute_path(path, load_path), from_import)
358
- return URL.path_to_file_url(resolved) unless resolved.nil?
359
- end
360
- nil
355
+ private
356
+
357
+ def resolve_file_url(path, parent_dir, from_import)
358
+ resolved = FileSystemImporter.resolve_path(File.absolute_path(path, parent_dir), from_import)
359
+ Uri.path_to_file_url(resolved) unless resolved.nil?
361
360
  end
362
361
 
363
362
  def syntax(path)
@@ -371,47 +370,55 @@ module SassC
371
370
  end
372
371
  end
373
372
 
374
- def imports_to_native(imports)
375
- {
373
+ def import_to_native(import, parent_dir, from_import, canonicalize)
374
+ if import.source
375
+ canonical_url = Uri.path_to_file_url(File.absolute_path(import.path, parent_dir))
376
+ @importer_results[canonical_url] = if import.source.is_a?(Hash)
377
+ {
378
+ contents: import.source[:contents],
379
+ syntax: import.source[:syntax],
380
+ source_map_url: canonical_url
381
+ }
382
+ else
383
+ {
384
+ contents: import.source,
385
+ syntax: syntax(import.path),
386
+ source_map_url: canonical_url
387
+ }
388
+ end
389
+ return canonical_url if canonicalize
390
+ elsif canonicalize
391
+ return resolve_file_url(import.path, parent_dir, from_import)
392
+ end
393
+
394
+ Uri.encode_uri_path_component(import.path)
395
+ end
396
+
397
+ def imports_to_native(imports, parent_dir, from_import, url, containing_url)
398
+ return import_to_native(imports.first, parent_dir, from_import, true) if imports.one?
399
+
400
+ canonical_url = "#{containing_url}?url=#{Uri.encode_uri_query_component(url)}&from_import=#{from_import}"
401
+ @importer_results[canonical_url] = {
376
402
  contents: imports.flat_map do |import|
377
- id = next_id
378
- canonical_url = URL.path_to_file_url(import.path)
379
- @canonical_urls[id] = canonical_url
380
- if import.source
381
- @importer_results[canonical_url] = if import.source.is_a?(Hash)
382
- {
383
- contents: import.source[:contents],
384
- syntax: import.source[:syntax],
385
- source_map_url: canonical_url
386
- }
387
- else
388
- {
389
- contents: import.source,
390
- syntax: syntax(import.path),
391
- source_map_url: canonical_url
392
- }
393
- end
394
- end
395
- [
396
- "@import \"#{Protocol::IMPORT}#{id}\";",
397
- "@import \"#{Protocol::LOADED}#{id}\";"
398
- ]
403
+ at_rule = from_import ? '@import' : '@forward'
404
+ url = import_to_native(import, parent_dir, from_import, false)
405
+ "#{at_rule} #{Script::Value::String.quote(url)};"
399
406
  end.join("\n"),
400
407
  syntax: :scss
401
408
  }
402
- end
403
409
 
404
- def next_id
405
- id = @id
406
- @id = id.next
407
- id.to_s
410
+ canonical_url
408
411
  end
409
412
  end
410
413
 
411
- private_constant :Importer
414
+ private_constant :ImportCache
412
415
  end
413
416
 
414
417
  class Sass2Scss
418
+ class << self
419
+ remove_method(:convert) if public_method_defined?(:convert, false)
420
+ end
421
+
415
422
  def self.convert(sass)
416
423
  {
417
424
  contents: sass,
@@ -421,7 +428,38 @@ module SassC
421
428
  end
422
429
 
423
430
  module Script
431
+ class Value
432
+ class String
433
+ class << self
434
+ remove_method(:quote) if public_method_defined?(:quote, false)
435
+ end
436
+
437
+ # Returns the quoted string representation of `contents`.
438
+ #
439
+ # @options opts :quote [String]
440
+ # The preferred quote style for quoted strings. If `:none`, strings are
441
+ # always emitted unquoted. If `nil`, quoting is determined automatically.
442
+ # @options opts :sass [String]
443
+ # Whether to quote strings for Sass source, as opposed to CSS. Defaults to `false`.
444
+ def self.quote(contents, opts = {})
445
+ contents = ::Sass::Value::String.new(contents, quoted: opts[:quote] != :none).to_s
446
+ opts[:sass] ? contents.gsub('#', '\#') : contents
447
+ end
448
+
449
+ remove_method(:to_s) if public_method_defined?(:to_s, false)
450
+
451
+ def to_s(opts = {})
452
+ opts = { quote: :none }.merge!(opts) if @type == :identifier
453
+ self.class.quote(@value, opts)
454
+ end
455
+ end
456
+ end
457
+
424
458
  module ValueConversion
459
+ class << self
460
+ remove_method(:from_native) if public_method_defined?(:from_native, false)
461
+ end
462
+
425
463
  def self.from_native(value, options)
426
464
  case value
427
465
  when ::Sass::Value::Null::NULL
@@ -477,6 +515,10 @@ module SassC
477
515
  end
478
516
  end
479
517
 
518
+ class << self
519
+ remove_method(:to_native) if public_method_defined?(:to_native, false)
520
+ end
521
+
480
522
  def self.to_native(value)
481
523
  case value
482
524
  when nil
@@ -537,48 +579,84 @@ module SassC
537
579
  end
538
580
  end
539
581
 
540
- module Protocol
541
- FILE = 'file:'
542
- IMPORT = 'sassc-embedded-import:'
543
- LOADED = 'sassc-embedded-loaded:'
544
- end
582
+ module Uri
583
+ module_function
545
584
 
546
- private_constant :Protocol
585
+ def parse(...)
586
+ ::URI::RFC3986_PARSER.parse(...)
587
+ end
547
588
 
548
- module URL
549
- PARSER = URI::Parser.new({ RESERVED: ';/?:@&=+$,' })
589
+ encode_uri_hash = {}
590
+ decode_uri_hash = {}
591
+ 256.times do |i|
592
+ c = -[i].pack('C')
593
+ h = c.unpack1('H')
594
+ l = c.unpack1('h')
595
+ pdd = -"%#{h}#{l}"
596
+ pdu = -"%#{h}#{l.upcase}"
597
+ pud = -"%#{h.upcase}#{l}"
598
+ puu = -pdd.upcase
599
+ encode_uri_hash[c] = puu
600
+ decode_uri_hash[pdd] = c
601
+ decode_uri_hash[pdu] = c
602
+ decode_uri_hash[pud] = c
603
+ decode_uri_hash[puu] = c
604
+ end.freeze
605
+ encode_uri_hash.freeze
606
+ decode_uri_hash.freeze
607
+
608
+ {
609
+ uri_path_component: "!$&'()*+,;=:/@",
610
+ uri_query_component: "!$&'()*+,;=:/?@",
611
+ uri_component: nil,
612
+ uri: "!$&'()*+,;=:/?#[]@"
613
+ }
614
+ .each do |symbol, unescaped|
615
+ encode_regexp = Regexp.new("[^0-9A-Za-z#{Regexp.escape("-._~#{unescaped}")}]", Regexp::NOENCODING)
616
+
617
+ define_method(:"encode_#{symbol}") do |str|
618
+ str.b.gsub(encode_regexp, encode_uri_hash).force_encoding(str.encoding)
619
+ end
550
620
 
551
- private_constant :PARSER
621
+ next if symbol.match?(/_.+_/o)
552
622
 
553
- module_function
623
+ decode_regexp = /%[0-9A-Fa-f]{2}/o
624
+ decode_uri_hash_with_preserve_escaped = if unescaped.nil? || unescaped.empty?
625
+ decode_uri_hash
626
+ else
627
+ decode_uri_hash.to_h do |key, value|
628
+ [key, unescaped.include?(value) ? key : value]
629
+ end.freeze
630
+ end
554
631
 
555
- def parse(str)
556
- PARSER.parse(str)
557
- end
632
+ define_method(:"decode_#{symbol}") do |str|
633
+ str.gsub(decode_regexp, decode_uri_hash_with_preserve_escaped).force_encoding(str.encoding)
634
+ end
635
+ end
558
636
 
559
- def escape(str)
560
- PARSER.escape(str)
637
+ def file_urls_to_relative_url(url, from_url)
638
+ parse(url).route_from(from_url).to_s
561
639
  end
562
640
 
563
- def unescape(str)
564
- PARSER.unescape(str)
641
+ def file_urls_to_relative_path(url, from_url)
642
+ decode_uri_component(file_urls_to_relative_url(url, from_url))
565
643
  end
566
644
 
567
645
  def file_url_to_path(url)
568
- return if url.nil?
569
-
570
- path = unescape(parse(url).path)
571
- path = path[1..] if Gem.win_platform? && path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':'
646
+ path = decode_uri_component(parse(url).path)
647
+ if path.start_with?('/')
648
+ windows_path = path[1..]
649
+ path = windows_path if File.absolute_path?(windows_path)
650
+ end
572
651
  path
573
652
  end
574
653
 
575
654
  def path_to_file_url(path)
576
- return if path.nil?
577
-
578
655
  path = "/#{path}" unless path.start_with?('/')
579
- URI::File.build([nil, escape(path)]).to_s
656
+
657
+ "file://#{encode_uri_path_component(path)}"
580
658
  end
581
659
  end
582
660
 
583
- private_constant :URL
661
+ private_constant :Uri
584
662
  end