sassc-embedded 1.63.0 → 1.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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