sass-embedded 0.14.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sass/compile_error.rb +1 -10
  3. data/lib/sass/compile_result.rb +1 -5
  4. data/lib/sass/embedded/channel.rb +2 -2
  5. data/lib/sass/embedded/compile_context/function_registry.rb +85 -0
  6. data/lib/sass/embedded/compile_context/importer_registry.rb +99 -0
  7. data/lib/sass/embedded/compile_context/logger_registry.rb +45 -0
  8. data/lib/sass/embedded/compile_context/value_protofier.rb +237 -0
  9. data/lib/sass/embedded/compile_context.rb +31 -238
  10. data/lib/sass/embedded/compiler/requirements.rb +1 -1
  11. data/lib/sass/embedded/compiler.rb +10 -43
  12. data/lib/sass/embedded/{render.rb → legacy.rb} +79 -3
  13. data/lib/sass/embedded/observer.rb +2 -0
  14. data/lib/sass/embedded/protofier.rb +91 -0
  15. data/lib/sass/embedded/structifier.rb +30 -0
  16. data/lib/sass/embedded/varint.rb +33 -0
  17. data/lib/sass/embedded/version.rb +1 -1
  18. data/lib/sass/embedded/version_context.rb +2 -3
  19. data/lib/sass/embedded.rb +39 -54
  20. data/lib/sass/embedded_protocol.rb +0 -6
  21. data/lib/sass/logger/source_location.rb +3 -9
  22. data/lib/sass/logger/source_span.rb +1 -13
  23. data/lib/sass/logger.rb +3 -3
  24. data/lib/sass/script_error.rb +5 -0
  25. data/lib/sass/value/argument_list.rb +24 -0
  26. data/lib/sass/value/boolean.rb +42 -0
  27. data/lib/sass/value/color.rb +213 -0
  28. data/lib/sass/value/function.rb +35 -0
  29. data/lib/sass/value/fuzzy_math.rb +81 -0
  30. data/lib/sass/value/list.rb +60 -0
  31. data/lib/sass/value/map.rb +57 -0
  32. data/lib/sass/value/null.rb +39 -0
  33. data/lib/sass/value/number/unit.rb +186 -0
  34. data/lib/sass/value/number.rb +272 -0
  35. data/lib/sass/value/string.rb +43 -0
  36. data/lib/sass/value.rb +92 -0
  37. data/lib/sass.rb +51 -48
  38. metadata +30 -56
  39. data/lib/sass/embedded/url.rb +0 -35
  40. data/lib/sass/file_importer.rb +0 -10
  41. data/lib/sass/importer.rb +0 -14
  42. data/lib/sass/importer_result.rb +0 -14
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../embedded_protocol'
4
- require_relative '../logger/source_span'
5
- require_relative 'observer'
6
-
7
3
  module Sass
8
4
  class Embedded
9
5
  # The {Observer} for {Embedded#compile}.
@@ -31,38 +27,33 @@ module Sass
31
27
  logger:,
32
28
  quiet_deps:,
33
29
  verbose:)
34
- @path = path
35
- @source = source
36
-
37
- @importer = to_struct(importer)
38
- @load_paths = load_paths
39
- @syntax = syntax
40
- @url = url
41
-
42
- @source_map = source_map
43
- @source_map_include_sources = source_map_include_sources
44
- @style = style
45
-
46
- @global_functions = functions.keys.map(&:to_s)
47
-
48
- @functions = functions.transform_keys do |key|
49
- key.to_s.split('(')[0].chomp
50
- end
51
- @importers = importers.map do |obj|
52
- to_struct(obj)
53
- end
54
-
55
- @alert_ascii = alert_ascii
56
- @alert_color = alert_color
57
-
58
- @logger = to_struct(logger)
59
-
60
- @quiet_deps = quiet_deps
61
- @verbose = verbose
30
+ @function_registery = FunctionRegistry.new(functions)
31
+ @importer_registery = ImporterRegistry.new(importers, load_paths)
32
+ @logger_registery = LoggerRegistry.new(logger)
62
33
 
63
34
  super(channel)
64
35
 
65
- send_message compile_request
36
+ send_message EmbeddedProtocol::InboundMessage::CompileRequest.new(
37
+ id: id,
38
+ string: unless source.nil?
39
+ EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
40
+ source: source,
41
+ url: url&.to_s,
42
+ syntax: Protofier.to_proto_syntax(syntax),
43
+ importer: importer.nil? ? nil : @importer_registery.register(importer)
44
+ )
45
+ end,
46
+ path: path,
47
+ style: Protofier.to_proto_output_style(style),
48
+ source_map: source_map,
49
+ source_map_include_sources: source_map_include_sources,
50
+ importers: @importer_registery.importers,
51
+ global_functions: @function_registery.global_functions,
52
+ alert_ascii: alert_ascii,
53
+ alert_color: alert_color,
54
+ quiet_deps: quiet_deps,
55
+ verbose: verbose
56
+ )
66
57
  end
67
58
 
68
59
  def update(error, message)
@@ -78,30 +69,30 @@ module Sass
78
69
  when EmbeddedProtocol::OutboundMessage::LogEvent
79
70
  return unless message.compilation_id == id
80
71
 
81
- log message
72
+ @logger_registery.log message
82
73
  when EmbeddedProtocol::OutboundMessage::CanonicalizeRequest
83
74
  return unless message.compilation_id == id
84
75
 
85
76
  Thread.new do
86
- send_message canonicalize_response message
77
+ send_message @importer_registery.canonicalize message
87
78
  end
88
79
  when EmbeddedProtocol::OutboundMessage::ImportRequest
89
80
  return unless message.compilation_id == id
90
81
 
91
82
  Thread.new do
92
- send_message import_response message
83
+ send_message @importer_registery.import message
93
84
  end
94
85
  when EmbeddedProtocol::OutboundMessage::FileImportRequest
95
86
  return unless message.compilation_id == id
96
87
 
97
88
  Thread.new do
98
- send_message file_import_response message
89
+ send_message @importer_registery.file_import message
99
90
  end
100
91
  when EmbeddedProtocol::OutboundMessage::FunctionCallRequest
101
92
  return unless message.compilation_id == id
102
93
 
103
94
  Thread.new do
104
- send_message function_call_response message
95
+ send_message @function_registery.function_call message
105
96
  end
106
97
  end
107
98
  rescue StandardError => e
@@ -109,206 +100,8 @@ module Sass
109
100
  super(e, nil)
110
101
  end
111
102
  end
112
-
113
- private
114
-
115
- def log(event)
116
- case event.type
117
- when :DEBUG
118
- if @logger.respond_to? :debug
119
- @logger.debug(event.message, span: Logger::SourceSpan.from_proto(event.span))
120
- else
121
- Kernel.warn(event.formatted)
122
- end
123
- when :DEPRECATION_WARNING
124
- if @logger.respond_to? :warn
125
- @logger.warn(event.message, deprecation: true,
126
- span: Logger::SourceSpan.from_proto(event.span),
127
- stack: event.stack_trace)
128
- else
129
- Kernel.warn(event.formatted)
130
- end
131
- when :WARNING
132
- if @logger.respond_to? :warn
133
- @logger.warn(event.message, deprecation: false,
134
- span: Logger::SourceSpan.from_proto(event.span),
135
- stack: event.stack_trace)
136
- else
137
- Kernel.warn(event.formatted)
138
- end
139
- end
140
- end
141
-
142
- def compile_request
143
- EmbeddedProtocol::InboundMessage::CompileRequest.new(
144
- id: id,
145
- string: unless @source.nil?
146
- EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
147
- source: @source,
148
- url: @url,
149
- syntax: to_proto_syntax(@syntax),
150
- importer: @importer.nil? ? nil : to_proto_importer(@importer, @importers.length)
151
- )
152
- end,
153
- path: @path,
154
- style: to_proto_output_style(@style),
155
- source_map: @source_map,
156
- source_map_include_sources: @source_map_include_sources,
157
- importers: to_proto_importers(@importers, @load_paths),
158
- global_functions: @global_functions,
159
- alert_ascii: @alert_ascii,
160
- alert_color: @alert_color
161
- )
162
- end
163
-
164
- def canonicalize_response(canonicalize_request)
165
- importer = importer_of_id canonicalize_request.importer_id
166
- url = importer.canonicalize canonicalize_request.url,
167
- from_import: canonicalize_request.from_import
168
-
169
- EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
170
- id: canonicalize_request.id,
171
- url: url
172
- )
173
- rescue StandardError => e
174
- EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
175
- id: canonicalize_request.id,
176
- error: e.message
177
- )
178
- end
179
-
180
- def import_response(import_request)
181
- importer = importer_of_id import_request.importer_id
182
- importer_result = to_struct importer.load(import_request.url)
183
-
184
- EmbeddedProtocol::InboundMessage::ImportResponse.new(
185
- id: import_request.id,
186
- success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
187
- contents: importer_result.contents,
188
- syntax: to_proto_syntax(importer_result.syntax),
189
- source_map_url: importer_result.respond_to?(:source_map_url) ? importer_result.source_map_url : nil
190
- )
191
- )
192
- rescue StandardError => e
193
- EmbeddedProtocol::InboundMessage::ImportResponse.new(
194
- id: import_request.id,
195
- error: e.message
196
- )
197
- end
198
-
199
- def file_import_response(file_import_request)
200
- importer = importer_of_id file_import_request.importer_id
201
- file_url = importer.find_file_url file_import_request.url,
202
- from_import: file_import_request.from_import
203
-
204
- raise "file_url must be a file: URL, was \"#{file_url}\"" if !file_url.nil? && !file_url.start_with?('file:')
205
-
206
- EmbeddedProtocol::InboundMessage::FileImportResponse.new(
207
- id: file_import_request.id,
208
- file_url: file_url
209
- )
210
- rescue StandardError => e
211
- EmbeddedProtocol::InboundMessage::FileImportResponse.new(
212
- id: file_import_request.id,
213
- error: e.message
214
- )
215
- end
216
-
217
- def function_call_response(function_call_request)
218
- EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
219
- id: function_call_request.id,
220
- success: @functions[function_call_request.name].call(*function_call_request.arguments),
221
- accessed_argument_lists: function_call_request.arguments
222
- .filter { |argument| argument.value == :argument_list }
223
- .map { |argument| argument.argument_list.id }
224
- )
225
- rescue StandardError => e
226
- EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
227
- id: function_call_request.id,
228
- error: e.message
229
- )
230
- end
231
-
232
- def to_proto_syntax(syntax)
233
- case syntax&.to_sym
234
- when :scss
235
- EmbeddedProtocol::Syntax::SCSS
236
- when :indented
237
- EmbeddedProtocol::Syntax::INDENTED
238
- when :css
239
- EmbeddedProtocol::Syntax::CSS
240
- else
241
- raise ArgumentError, 'syntax must be one of :scss, :indented, :css'
242
- end
243
- end
244
-
245
- def to_proto_output_style(style)
246
- case style&.to_sym
247
- when :expanded
248
- EmbeddedProtocol::OutputStyle::EXPANDED
249
- when :compressed
250
- EmbeddedProtocol::OutputStyle::COMPRESSED
251
- else
252
- raise ArgumentError, 'style must be one of :expanded, :compressed'
253
- end
254
- end
255
-
256
- def to_proto_importer(importer, id)
257
- is_importer = importer.respond_to?(:canonicalize) && importer.respond_to?(:load)
258
- is_file_importer = importer.respond_to?(:find_file_url)
259
-
260
- if is_importer && !is_file_importer
261
- EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
262
- importer_id: id
263
- )
264
- elsif is_file_importer && !is_importer
265
- EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
266
- file_importer_id: id
267
- )
268
- else
269
- raise ArgumentError, 'importer must be an Importer or a FileImporter'
270
- end
271
- end
272
-
273
- def to_proto_importers(importers, load_paths)
274
- proto_importers = importers.map.with_index do |importer, id|
275
- to_proto_importer(importer, id)
276
- end
277
-
278
- load_paths.each do |load_path|
279
- proto_importers << EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
280
- path: File.absolute_path(load_path)
281
- )
282
- end
283
-
284
- proto_importers
285
- end
286
-
287
- def importer_of_id(id)
288
- if id == @importers.length
289
- @importer
290
- else
291
- @importers[id]
292
- end
293
- end
294
-
295
- def to_struct(obj)
296
- return obj unless obj.is_a? Hash
297
-
298
- struct = Object.new
299
- obj.each do |key, value|
300
- if value.respond_to? :call
301
- struct.define_singleton_method key.to_sym do |*args, **kwargs|
302
- value.call(*args, **kwargs)
303
- end
304
- else
305
- struct.define_singleton_method key.to_sym do
306
- value
307
- end
308
- end
309
- end
310
- struct
311
- end
312
103
  end
104
+
105
+ private_constant :CompileContext
313
106
  end
314
107
  end
@@ -3,7 +3,7 @@
3
3
  module Sass
4
4
  class Embedded
5
5
  class Compiler
6
- REQUIREMENTS = '~> 1.49.7'
6
+ REQUIREMENTS = '~> 1.49.8'
7
7
  end
8
8
  end
9
9
  end
@@ -2,9 +2,6 @@
2
2
 
3
3
  require 'observer'
4
4
  require 'open3'
5
- require_relative '../embedded_protocol'
6
- require_relative 'compiler/path'
7
- require_relative 'protocol_error'
8
5
 
9
6
  module Sass
10
7
  class Embedded
@@ -14,14 +11,7 @@ module Sass
14
11
  class Compiler
15
12
  include Observable
16
13
 
17
- ONEOF_MESSAGE = EmbeddedProtocol::InboundMessage
18
- .descriptor
19
- .lookup_oneof('message')
20
- .to_h do |field_descriptor|
21
- [field_descriptor.subtype, field_descriptor.name]
22
- end
23
-
24
- private_constant :ONEOF_MESSAGE
14
+ PROTOCOL_ERROR_ID = 4_294_967_295
25
15
 
26
16
  def initialize
27
17
  @observerable_mutex = Mutex.new
@@ -35,7 +25,7 @@ module Sass
35
25
  warn(@stderr.readline, uplevel: 1)
36
26
  end
37
27
  poll do
38
- receive_proto read
28
+ receive_message Protofier.from_proto_message read
39
29
  end
40
30
  end
41
31
 
@@ -60,9 +50,7 @@ module Sass
60
50
  end
61
51
 
62
52
  def send_message(message)
63
- write EmbeddedProtocol::InboundMessage.new(
64
- ONEOF_MESSAGE[message.class.descriptor] => message
65
- ).to_proto
53
+ write Protofier.to_proto_message message
66
54
  end
67
55
 
68
56
  def close
@@ -86,7 +74,7 @@ module Sass
86
74
  private
87
75
 
88
76
  def half_closed?
89
- @id == EmbeddedProtocol::PROTOCOL_ERROR_ID
77
+ @id == PROTOCOL_ERROR_ID
90
78
  end
91
79
 
92
80
  def poll
@@ -108,9 +96,7 @@ module Sass
108
96
  end
109
97
  end
110
98
 
111
- def receive_proto(proto)
112
- payload = EmbeddedProtocol::OutboundMessage.decode(proto)
113
- message = payload[payload.message.to_s]
99
+ def receive_message(message)
114
100
  case message
115
101
  when EmbeddedProtocol::ProtocolError
116
102
  raise ProtocolError, message.message
@@ -120,37 +106,18 @@ module Sass
120
106
  end
121
107
 
122
108
  def read
123
- length = read_varint(@stdout)
109
+ length = Varint.read(@stdout)
124
110
  @stdout.read(length)
125
111
  end
126
112
 
127
113
  def write(payload)
128
114
  @stdin_mutex.synchronize do
129
- write_varint(@stdin, payload.length)
130
- @stdin.write payload
131
- end
132
- end
133
-
134
- def read_varint(readable)
135
- value = bits = 0
136
- loop do
137
- byte = readable.readbyte
138
- value |= (byte & 0x7f) << bits
139
- bits += 7
140
- break if byte < 0x80
115
+ Varint.write(@stdin, payload.length)
116
+ @stdin.write(payload)
141
117
  end
142
- value
143
- end
144
-
145
- def write_varint(writeable, value)
146
- bytes = []
147
- until value < 0x80
148
- bytes << (0x80 | (value & 0x7f))
149
- value >>= 7
150
- end
151
- bytes << value
152
- writeable.write bytes.pack('C*')
153
118
  end
154
119
  end
120
+
121
+ private_constant :Compiler
155
122
  end
156
123
  end
@@ -3,9 +3,50 @@
3
3
  require 'base64'
4
4
  require 'json'
5
5
  require 'pathname'
6
- require_relative 'url'
6
+ require 'uri'
7
7
 
8
+ # The Sass module.
9
+ #
10
+ # This communicates with Embedded Dart Sass using the Embedded Sass protocol.
8
11
  module Sass
12
+ class << self
13
+ # @deprecated
14
+ # The global {.include_paths} for Sass files. This is meant for plugins and
15
+ # libraries to register the paths to their Sass stylesheets to that they may
16
+ # be included via `@import` or `@use`. This include path is used by every
17
+ # instance of {Sass::Embedded}. They are lower-precedence than any include
18
+ # paths passed in via the `include_paths` option.
19
+ #
20
+ # If the `SASS_PATH` environment variable is set,
21
+ # the initial value of `include_paths` will be initialized based on that.
22
+ # The variable should be a colon-separated list of path names
23
+ # (semicolon-separated on Windows).
24
+ #
25
+ # @example
26
+ # Sass.include_paths << File.dirname(__FILE__) + '/sass'
27
+ # @return [Array]
28
+ def include_paths
29
+ Embedded.include_paths
30
+ end
31
+
32
+ # @deprecated
33
+ # The global {.render} method. This instantiates a global {Embedded} instance
34
+ # and calls {Embedded#render}.
35
+ #
36
+ # See {Embedded#render} for supported options.
37
+ #
38
+ # @example
39
+ # Sass.render(data: 'h1 { font-size: 40px; }')
40
+ # @example
41
+ # Sass.render(file: 'style.css')
42
+ # @return [Result]
43
+ # @raise [ProtocolError]
44
+ # @raise [RenderError]
45
+ def render(**kwargs)
46
+ instance.render(**kwargs)
47
+ end
48
+ end
49
+
9
50
  # The {Embedded} host for using dart-sass-embedded. Each instance creates
10
51
  # its own {Channel}.
11
52
  #
@@ -309,11 +350,17 @@ module Sass
309
350
  raise result if result.is_a? StandardError
310
351
 
311
352
  if result&.key? :contents
312
- @importer_results[canonical_url] = ImporterResult.new(result[:contents], :scss)
353
+ @importer_results[canonical_url] = {
354
+ contents: result[:contents],
355
+ syntax: :scss
356
+ }
313
357
  canonical_url
314
358
  elsif result&.key? :file
315
359
  canonical_url = Url.path_to_file_url(File.absolute_path(result[:file]))
316
- @importer_results[canonical_url] = ImporterResult.new(File.read(result[:file]), :scss)
360
+ @importer_results[canonical_url] = {
361
+ contents: File.read(result[:file]),
362
+ syntax: :scss
363
+ }
317
364
  canonical_url
318
365
  end
319
366
  end
@@ -323,6 +370,35 @@ module Sass
323
370
  end
324
371
  end
325
372
 
373
+ # @deprecated
374
+ # The {Url} module.
375
+ module Url
376
+ # The {::URI::Parser} that is in consistent with RFC 2396 (URI Generic Syntax) and dart:core library.
377
+ URI_PARSER = URI::Parser.new({ RESERVED: ';/?:@&=+$,' })
378
+
379
+ FILE_SCHEME = 'file://'
380
+
381
+ private_constant :URI_PARSER, :FILE_SCHEME
382
+
383
+ module_function
384
+
385
+ def path_to_file_url(path)
386
+ if File.absolute_path? path
387
+ URI_PARSER.escape "#{FILE_SCHEME}#{Gem.win_platform? ? '/' : ''}#{path}"
388
+ else
389
+ URI_PARSER.escape path
390
+ end
391
+ end
392
+
393
+ def file_url_to_path(url)
394
+ if url.start_with? FILE_SCHEME
395
+ URI_PARSER.unescape url[(FILE_SCHEME.length + (Gem.win_platform? ? 1 : 0))..]
396
+ else
397
+ URI_PARSER.unescape url
398
+ end
399
+ end
400
+ end
401
+
326
402
  private_constant :LegacyImporter
327
403
  end
328
404
  end
@@ -43,5 +43,7 @@ module Sass
43
43
  @subscription.send_message(*args)
44
44
  end
45
45
  end
46
+
47
+ private_constant :Observer
46
48
  end
47
49
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ class Embedded
5
+ # The {Protofier} between Pure Ruby types and Protobuf Ruby types.
6
+ module Protofier
7
+ ONEOF_MESSAGE = EmbeddedProtocol::InboundMessage
8
+ .descriptor
9
+ .lookup_oneof('message')
10
+ .to_h do |field_descriptor|
11
+ [field_descriptor.subtype, field_descriptor.name]
12
+ end
13
+
14
+ private_constant :ONEOF_MESSAGE
15
+
16
+ module_function
17
+
18
+ def from_proto_compile_response(compile_response)
19
+ if compile_response.result == :failure
20
+ raise CompileError.new(
21
+ compile_response.failure.formatted || compile_response.failure.message,
22
+ compile_response.failure.message,
23
+ compile_response.failure.stack_trace,
24
+ from_proto_source_span(compile_response.failure.span)
25
+ )
26
+ end
27
+
28
+ CompileResult.new(
29
+ compile_response.success.css,
30
+ compile_response.success.source_map,
31
+ compile_response.success.loaded_urls
32
+ )
33
+ end
34
+
35
+ def from_proto_source_span(source_span)
36
+ return nil if source_span.nil?
37
+
38
+ Logger::SourceSpan.new(from_proto_source_location(source_span.start),
39
+ from_proto_source_location(source_span.end),
40
+ source_span.text,
41
+ source_span.url,
42
+ source_span.context)
43
+ end
44
+
45
+ def from_proto_source_location(source_location)
46
+ return nil if source_location.nil?
47
+
48
+ Logger::SourceLocation.new(source_location.offset,
49
+ source_location.line,
50
+ source_location.column)
51
+ end
52
+
53
+ def from_proto_message(proto)
54
+ message = EmbeddedProtocol::OutboundMessage.decode(proto)
55
+ message.method(message.message).call
56
+ end
57
+
58
+ def to_proto_message(message)
59
+ EmbeddedProtocol::InboundMessage.new(
60
+ ONEOF_MESSAGE[message.class.descriptor] => message
61
+ ).to_proto
62
+ end
63
+
64
+ def to_proto_syntax(syntax)
65
+ case syntax&.to_sym
66
+ when :scss
67
+ EmbeddedProtocol::Syntax::SCSS
68
+ when :indented
69
+ EmbeddedProtocol::Syntax::INDENTED
70
+ when :css
71
+ EmbeddedProtocol::Syntax::CSS
72
+ else
73
+ raise ArgumentError, 'syntax must be one of :scss, :indented, :css'
74
+ end
75
+ end
76
+
77
+ def to_proto_output_style(style)
78
+ case style&.to_sym
79
+ when :expanded
80
+ EmbeddedProtocol::OutputStyle::EXPANDED
81
+ when :compressed
82
+ EmbeddedProtocol::OutputStyle::COMPRESSED
83
+ else
84
+ raise ArgumentError, 'style must be one of :expanded, :compressed'
85
+ end
86
+ end
87
+ end
88
+
89
+ private_constant :Protofier
90
+ end
91
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ class Embedded
5
+ # The {Structifier} that convert {::Hash} to {Struct}-like object.
6
+ module Structifier
7
+ module_function
8
+
9
+ def to_struct(obj)
10
+ return obj unless obj.is_a? Hash
11
+
12
+ struct = Object.new
13
+ obj.each do |key, value|
14
+ if value.respond_to? :call
15
+ struct.define_singleton_method key.to_sym do |*args, **kwargs|
16
+ value.call(*args, **kwargs)
17
+ end
18
+ else
19
+ struct.define_singleton_method key.to_sym do
20
+ value
21
+ end
22
+ end
23
+ end
24
+ struct
25
+ end
26
+ end
27
+
28
+ private_constant :Structifier
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ class Embedded
5
+ # Read and write {Varint} from {::IO}.
6
+ module Varint
7
+ module_function
8
+
9
+ def read(readable)
10
+ value = bits = 0
11
+ loop do
12
+ byte = readable.readbyte
13
+ value |= (byte & 0x7f) << bits
14
+ bits += 7
15
+ break if byte < 0x80
16
+ end
17
+ value
18
+ end
19
+
20
+ def write(writeable, value)
21
+ bytes = []
22
+ until value < 0x80
23
+ bytes << (0x80 | (value & 0x7f))
24
+ value >>= 7
25
+ end
26
+ bytes << value
27
+ writeable.write bytes.pack('C*')
28
+ end
29
+ end
30
+
31
+ private_constant :Varint
32
+ end
33
+ end