sassc-embedded 1.63.0 → 1.80.1

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 +282 -203
  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,19 +128,17 @@ 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
143
- def setup(_native_options, functions: Script::Functions)
144
- @callbacks = {}
139
+ remove_method(:setup) if public_method_defined?(:setup, false)
145
140
 
141
+ def setup(_native_options, functions: Script::Functions)
146
142
  functions_wrapper = Class.new do
147
143
  attr_accessor :options
148
144
 
@@ -150,7 +146,7 @@ module SassC
150
146
  end.new
151
147
  functions_wrapper.options = @options
152
148
 
153
- Script.custom_functions(functions: functions).each do |custom_function|
149
+ Script.custom_functions(functions:).each_with_object({}) do |custom_function, callbacks|
154
150
  callback = lambda do |native_argument_list|
155
151
  function_arguments = arguments_from_native_list(native_argument_list)
156
152
  begin
@@ -164,48 +160,72 @@ module SassC
164
160
  raise e
165
161
  end
166
162
 
167
- @callbacks[Script.formatted_function_name(custom_function, functions: functions)] = callback
163
+ callbacks[Script.formatted_function_name(custom_function, functions:)] = callback
168
164
  end
169
-
170
- @callbacks
171
165
  end
172
166
 
173
167
  private
174
168
 
169
+ remove_method(:arguments_from_native_list) if private_method_defined?(:arguments_from_native_list, false)
170
+
175
171
  def arguments_from_native_list(native_argument_list)
176
172
  native_argument_list.filter_map do |native_value|
177
173
  Script::ValueConversion.from_native(native_value, @options)
178
174
  end
179
175
  end
176
+ end
180
177
 
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
178
+ module NoopImporter
179
+ module_function
180
+
181
+ def canonicalize(...); end
182
+
183
+ def load(...); end
201
184
  end
202
185
 
186
+ private_constant :NoopImporter
187
+
203
188
  class ImportHandler
189
+ remove_method(:setup) if public_method_defined?(:setup, false)
190
+
204
191
  def setup(_native_options)
205
- Importer.new(@importer) if @importer
192
+ if @importer
193
+ import_cache = ImportCache.new(@importer)
194
+ [Importer.new(import_cache), FileImporter.new(import_cache)]
195
+ else
196
+ []
197
+ end
206
198
  end
207
199
 
200
+ class Importer
201
+ def initialize(import_cache)
202
+ @import_cache = import_cache
203
+ end
204
+
205
+ def canonicalize(...)
206
+ @import_cache.canonicalize(...)
207
+ end
208
+
209
+ def load(...)
210
+ @import_cache.load(...)
211
+ end
212
+ end
213
+
214
+ private_constant :Importer
215
+
208
216
  class FileImporter
217
+ def initialize(import_cache)
218
+ @import_cache = import_cache
219
+ end
220
+
221
+ def find_file_url(...)
222
+ @import_cache.find_file_url(...)
223
+ end
224
+ end
225
+
226
+ private_constant :FileImporter
227
+
228
+ module FileSystemImporter
209
229
  class << self
210
230
  def resolve_path(path, from_import)
211
231
  ext = File.extname(path)
@@ -217,15 +237,6 @@ module SassC
217
237
  return exactly_one(try_path(path))
218
238
  end
219
239
 
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
240
  if from_import
230
241
  result = exactly_one(try_path_with_ext("#{path}.import"))
231
242
  return result unless result.nil?
@@ -253,7 +264,7 @@ module SassC
253
264
  end
254
265
 
255
266
  def try_path_as_dir(path, from_import)
256
- return unless dir_exist? path
267
+ return unless dir_exist?(path)
257
268
 
258
269
  if from_import
259
270
  result = exactly_one(try_path_with_ext(File.join(path, 'index.import')))
@@ -265,7 +276,7 @@ module SassC
265
276
 
266
277
  def exactly_one(paths)
267
278
  return if paths.empty?
268
- return paths.first if paths.length == 1
279
+ return paths.first if paths.one?
269
280
 
270
281
  raise "It's not clear which file to import. Found:\n#{paths.map { |path| " #{path}" }.join("\n")}"
271
282
  end
@@ -285,79 +296,63 @@ module SassC
285
296
  end
286
297
  end
287
298
 
288
- private_constant :FileImporter
299
+ private_constant :FileSystemImporter
289
300
 
290
- class Importer
301
+ class ImportCache
291
302
  def initialize(importer)
292
303
  @importer = importer
293
-
294
- @canonical_urls = {}
295
- @id = 0
296
304
  @importer_results = {}
297
- @parent_urls = [URL.path_to_file_url(File.absolute_path(@importer.options[:filename] || 'stdin'))]
305
+ @importer_result = nil
306
+ @file_url = nil
298
307
  end
299
308
 
300
- def canonicalize(url, from_import:)
301
- if url.start_with?(Protocol::IMPORT)
302
- canonical_url = @canonical_urls.delete(url.delete_prefix(Protocol::IMPORT))
309
+ def canonicalize(url, context)
310
+ return unless context.containing_url&.start_with?('file:')
311
+
312
+ containing_url = context.containing_url
313
+
314
+ path = Uri.decode_uri_component(url)
315
+ parent_path = Uri.file_url_to_path(containing_url)
316
+ parent_dir = File.dirname(parent_path)
317
+
318
+ if containing_url.include?('?')
319
+ canonical_url = Uri.path_to_file_url(File.absolute_path(path, parent_dir))
303
320
  unless @importer_results.key?(canonical_url)
304
- canonical_url = resolve_file_url(canonical_url, @parent_urls.last, from_import)
321
+ @file_url = resolve_file_url(path, parent_dir, context.from_import)
322
+ return
305
323
  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))
324
+ else
325
+ imports = [*@importer.imports(path, parent_path)]
326
+ canonical_url = imports_to_native(imports, parent_dir, context.from_import, url, containing_url)
327
+ unless @importer_results.key?(canonical_url)
328
+ @file_url = canonical_url
329
+ return
317
330
  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
331
  end
332
+
333
+ @importer_result = @importer_results.delete(canonical_url)
334
+ canonical_url
327
335
  end
328
336
 
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
337
+ def load(_canonical_url)
338
+ importer_result = @importer_result
339
+ @importer_result = nil
340
+ importer_result
345
341
  end
346
342
 
347
- private
343
+ def find_file_url(_url, context)
344
+ return if context.containing_url.nil? || @file_url.nil?
348
345
 
349
- def load_paths
350
- @load_paths ||= (@importer.options[:load_paths] || []) + SassC.load_paths
346
+ canonical_url = @file_url
347
+ @file_url = nil
348
+ canonical_url
351
349
  end
352
350
 
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
351
+ private
352
+
353
+ def resolve_file_url(path, parent_dir, from_import)
354
+ resolved = FileSystemImporter.resolve_path(File.absolute_path(path, parent_dir), from_import)
355
+ Uri.path_to_file_url(resolved) unless resolved.nil?
361
356
  end
362
357
 
363
358
  def syntax(path)
@@ -371,47 +366,55 @@ module SassC
371
366
  end
372
367
  end
373
368
 
374
- def imports_to_native(imports)
375
- {
369
+ def import_to_native(import, parent_dir, from_import, canonicalize)
370
+ if import.source
371
+ canonical_url = Uri.path_to_file_url(File.absolute_path(import.path, parent_dir))
372
+ @importer_results[canonical_url] = if import.source.is_a?(Hash)
373
+ {
374
+ contents: import.source[:contents],
375
+ syntax: import.source[:syntax],
376
+ source_map_url: canonical_url
377
+ }
378
+ else
379
+ {
380
+ contents: import.source,
381
+ syntax: syntax(import.path),
382
+ source_map_url: canonical_url
383
+ }
384
+ end
385
+ return canonical_url if canonicalize
386
+ elsif canonicalize
387
+ return resolve_file_url(import.path, parent_dir, from_import)
388
+ end
389
+
390
+ Uri.encode_uri_path_component(import.path)
391
+ end
392
+
393
+ def imports_to_native(imports, parent_dir, from_import, url, containing_url)
394
+ return import_to_native(imports.first, parent_dir, from_import, true) if imports.one?
395
+
396
+ canonical_url = "#{containing_url}?url=#{Uri.encode_uri_query_component(url)}&from_import=#{from_import}"
397
+ @importer_results[canonical_url] = {
376
398
  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
- ]
399
+ at_rule = from_import ? '@import' : '@forward'
400
+ url = import_to_native(import, parent_dir, from_import, false)
401
+ "#{at_rule} #{Script::Value::String.quote(url)};"
399
402
  end.join("\n"),
400
403
  syntax: :scss
401
404
  }
402
- end
403
405
 
404
- def next_id
405
- id = @id
406
- @id = id.next
407
- id.to_s
406
+ canonical_url
408
407
  end
409
408
  end
410
409
 
411
- private_constant :Importer
410
+ private_constant :ImportCache
412
411
  end
413
412
 
414
413
  class Sass2Scss
414
+ class << self
415
+ remove_method(:convert) if public_method_defined?(:convert, false)
416
+ end
417
+
415
418
  def self.convert(sass)
416
419
  {
417
420
  contents: sass,
@@ -421,7 +424,38 @@ module SassC
421
424
  end
422
425
 
423
426
  module Script
427
+ class Value
428
+ class String
429
+ class << self
430
+ remove_method(:quote) if public_method_defined?(:quote, false)
431
+ end
432
+
433
+ # Returns the quoted string representation of `contents`.
434
+ #
435
+ # @options opts :quote [String]
436
+ # The preferred quote style for quoted strings. If `:none`, strings are
437
+ # always emitted unquoted. If `nil`, quoting is determined automatically.
438
+ # @options opts :sass [String]
439
+ # Whether to quote strings for Sass source, as opposed to CSS. Defaults to `false`.
440
+ def self.quote(contents, opts = {})
441
+ contents = ::Sass::Value::String.new(contents, quoted: opts[:quote] != :none).to_s
442
+ opts[:sass] ? contents.gsub('#', '\#') : contents
443
+ end
444
+
445
+ remove_method(:to_s) if public_method_defined?(:to_s, false)
446
+
447
+ def to_s(opts = {})
448
+ opts = { quote: :none }.merge!(opts) if @type == :identifier
449
+ self.class.quote(@value, opts)
450
+ end
451
+ end
452
+ end
453
+
424
454
  module ValueConversion
455
+ class << self
456
+ remove_method(:from_native) if public_method_defined?(:from_native, false)
457
+ end
458
+
425
459
  def self.from_native(value, options)
426
460
  case value
427
461
  when ::Sass::Value::Null::NULL
@@ -429,18 +463,21 @@ module SassC
429
463
  when ::Sass::Value::Boolean
430
464
  ::SassC::Script::Value::Bool.new(value.to_bool)
431
465
  when ::Sass::Value::Color
432
- if value.instance_eval { defined? @hue }
466
+ case value.space
467
+ when 'hsl', 'hwb'
468
+ value = value.to_space('hsl')
433
469
  ::SassC::Script::Value::Color.new(
434
- hue: value.hue,
435
- saturation: value.saturation,
436
- lightness: value.lightness,
470
+ hue: value.channel('hue'),
471
+ saturation: value.channel('saturation'),
472
+ lightness: value.channel('lightness'),
437
473
  alpha: value.alpha
438
474
  )
439
475
  else
476
+ value = value.to_space('rgb')
440
477
  ::SassC::Script::Value::Color.new(
441
- red: value.red,
442
- green: value.green,
443
- blue: value.blue,
478
+ red: value.channel('red'),
479
+ green: value.channel('green'),
480
+ blue: value.channel('blue'),
444
481
  alpha: value.alpha
445
482
  )
446
483
  end
@@ -459,7 +496,7 @@ module SassC
459
496
  )
460
497
  when ::Sass::Value::Map
461
498
  ::SassC::Script::Value::Map.new(
462
- value.contents.to_a.to_h { |k, v| [from_native(k, options), from_native(v, options)] }
499
+ value.contents.each_with_object({}) { |(k, v), h| h[from_native(k, options)] = from_native(v, options) }
463
500
  )
464
501
  when ::Sass::Value::Number
465
502
  ::SassC::Script::Value::Number.new(
@@ -477,6 +514,10 @@ module SassC
477
514
  end
478
515
  end
479
516
 
517
+ class << self
518
+ remove_method(:to_native) if public_method_defined?(:to_native, false)
519
+ end
520
+
480
521
  def self.to_native(value)
481
522
  case value
482
523
  when nil
@@ -489,14 +530,16 @@ module SassC
489
530
  red: value.red,
490
531
  green: value.green,
491
532
  blue: value.blue,
492
- alpha: value.alpha
533
+ alpha: value.alpha,
534
+ space: 'rgb'
493
535
  )
494
536
  elsif value.hlsa?
495
537
  ::Sass::Value::Color.new(
496
538
  hue: value.hue,
497
539
  saturation: value.saturation,
498
540
  lightness: value.lightness,
499
- alpha: value.alpha
541
+ alpha: value.alpha,
542
+ space: 'hsl'
500
543
  )
501
544
  else
502
545
  raise UnsupportedValue, "Sass color mode #{value.instance_eval { @mode }} unsupported"
@@ -516,7 +559,7 @@ module SassC
516
559
  )
517
560
  when ::SassC::Script::Value::Map
518
561
  ::Sass::Value::Map.new(
519
- value.value.to_a.to_h { |k, v| [to_native(k), to_native(v)] }
562
+ value.value.each_with_object({}) { |(k, v), h| h[to_native(k)] = to_native(v) }
520
563
  )
521
564
  when ::SassC::Script::Value::Number
522
565
  ::Sass::Value::Number.new(
@@ -537,48 +580,84 @@ module SassC
537
580
  end
538
581
  end
539
582
 
540
- module Protocol
541
- FILE = 'file:'
542
- IMPORT = 'sassc-embedded-import:'
543
- LOADED = 'sassc-embedded-loaded:'
544
- end
583
+ module Uri
584
+ module_function
545
585
 
546
- private_constant :Protocol
586
+ def parse(...)
587
+ ::URI::RFC3986_PARSER.parse(...)
588
+ end
547
589
 
548
- module URL
549
- PARSER = URI::Parser.new({ RESERVED: ';/?:@&=+$,' })
590
+ encode_uri_hash = {}
591
+ decode_uri_hash = {}
592
+ 256.times do |i|
593
+ c = -[i].pack('C')
594
+ h = c.unpack1('H')
595
+ l = c.unpack1('h')
596
+ pdd = -"%#{h}#{l}"
597
+ pdu = -"%#{h}#{l.upcase}"
598
+ pud = -"%#{h.upcase}#{l}"
599
+ puu = -pdd.upcase
600
+ encode_uri_hash[c] = puu
601
+ decode_uri_hash[pdd] = c
602
+ decode_uri_hash[pdu] = c
603
+ decode_uri_hash[pud] = c
604
+ decode_uri_hash[puu] = c
605
+ end.freeze
606
+ encode_uri_hash.freeze
607
+ decode_uri_hash.freeze
608
+
609
+ {
610
+ uri_path_component: "!$&'()*+,;=:/@",
611
+ uri_query_component: "!$&'()*+,;=:/?@",
612
+ uri_component: nil,
613
+ uri: "!$&'()*+,;=:/?#[]@"
614
+ }
615
+ .each do |symbol, unescaped|
616
+ encode_regexp = Regexp.new("[^0-9A-Za-z#{Regexp.escape("-._~#{unescaped}")}]", Regexp::NOENCODING)
617
+
618
+ define_method(:"encode_#{symbol}") do |str|
619
+ str.b.gsub(encode_regexp, encode_uri_hash).force_encoding(str.encoding)
620
+ end
550
621
 
551
- private_constant :PARSER
622
+ next if symbol.match?(/_.+_/o)
552
623
 
553
- module_function
624
+ decode_regexp = /%[0-9A-Fa-f]{2}/o
625
+ decode_uri_hash_with_preserve_escaped = if unescaped.nil? || unescaped.empty?
626
+ decode_uri_hash
627
+ else
628
+ decode_uri_hash.each_with_object({}) do |(key, value), hash|
629
+ hash[key] = unescaped.include?(value) ? key : value
630
+ end.freeze
631
+ end
554
632
 
555
- def parse(str)
556
- PARSER.parse(str)
557
- end
633
+ define_method(:"decode_#{symbol}") do |str|
634
+ str.gsub(decode_regexp, decode_uri_hash_with_preserve_escaped).force_encoding(str.encoding)
635
+ end
636
+ end
558
637
 
559
- def escape(str)
560
- PARSER.escape(str)
638
+ def file_urls_to_relative_url(url, from_url)
639
+ parse(url).route_from(from_url).to_s
561
640
  end
562
641
 
563
- def unescape(str)
564
- PARSER.unescape(str)
642
+ def file_urls_to_relative_path(url, from_url)
643
+ decode_uri_component(file_urls_to_relative_url(url, from_url))
565
644
  end
566
645
 
567
646
  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 == ':'
647
+ path = decode_uri_component(parse(url).path)
648
+ if path.start_with?('/')
649
+ windows_path = path[1..]
650
+ path = windows_path if File.absolute_path?(windows_path)
651
+ end
572
652
  path
573
653
  end
574
654
 
575
655
  def path_to_file_url(path)
576
- return if path.nil?
577
-
578
656
  path = "/#{path}" unless path.start_with?('/')
579
- URI::File.build([nil, escape(path)]).to_s
657
+
658
+ "file://#{encode_uri_path_component(path)}"
580
659
  end
581
660
  end
582
661
 
583
- private_constant :URL
662
+ private_constant :Uri
584
663
  end