sass-embedded 0.4.2 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c03a96231c8305e27f3deb1a35e308808482159a5dd25b4c8191da9c1b25500d
4
- data.tar.gz: d6ba46dd80433759554a8c83d1fe8a8b4fd66461ffb063c859011d4ab4e3b6cc
3
+ metadata.gz: d1086f5483670120b40e9741187e5150a5af9e9ad5d38159d6d57459edec350b
4
+ data.tar.gz: d6afead31f102a04ad4d11c82e1381b597c285ae0206564640763173e2b67fc6
5
5
  SHA512:
6
- metadata.gz: a4be81866a1672fd02da1cf1c66d08a2a0dd50cb002ee1b6fd0cce4d70c4e02222c11662a69307e7e33c58bf1dedc968d7ec72b2ebb3fa35296b1520896be2f5
7
- data.tar.gz: 39b348a11dd2277bb43e7bd936b08462057caac6180cb5b0548384cb64da47d1255606d8ce0a0ab2d0a4d891efee26cff190568eec1a782426011c4fc15ac2b0
6
+ metadata.gz: c8c534da41310d6b7e6228624d26a6e3dd94bccc4b67b2e9a25bff82251cb3b4a5d312c58ec1e498f944731cc99f4fdcb66b117278640cee2fc3d81f95924f66
7
+ data.tar.gz: 6d8808fc65dbb7fc4b43976beb6c8573635d874e9d72a7949f20eda5406e582ea0933a99f844256ec284e3ecf1e8e6f02b967f50854fde661470bf6834550abb
data/.rubocop.yml CHANGED
@@ -7,8 +7,5 @@ AllCops:
7
7
 
8
8
  NewCops: enable
9
9
 
10
- Layout/LineLength:
11
- Enabled: false
12
-
13
10
  Metrics:
14
11
  Enabled: false
data/ext/extconf.rb CHANGED
@@ -8,12 +8,24 @@ require 'fileutils'
8
8
  require_relative '../lib/sass/platform'
9
9
 
10
10
  module Sass
11
- # Install dependencies for sass-embedded during gem install
11
+ # The dependency downloader. This will download all the dependencies
12
+ # during gem installation, and the Makefile will unpack all downloaded
13
+ # dependencies. By default it will download the latest release of each
14
+ # dependency from GitHub releases.
15
+ #
16
+ # It is possible to specify an alternative source or version of each
17
+ # dependency. Local sources can be used for offline installation.
18
+ #
19
+ # @example
20
+ # gem install sass-embedded -- \
21
+ # --with-protoc=file:///path/to/protoc-*.zip
22
+ # --with-sass-embedded=file:///path/to/sass_embedded-*.(tar.gz|zip) \
23
+ # --with-sass-embedded-protocol=file:///path/to/embedded_sass.proto \
12
24
  class Extconf
13
25
  def initialize
26
+ get_with_config('protoc', true) { latest_protoc }
14
27
  get_with_config('sass-embedded', true) { latest_sass_embedded }
15
28
  get_with_config('sass-embedded-protocol', true) { latest_sass_embedded_protocol }
16
- get_with_config('protoc', true) { latest_protoc }
17
29
  end
18
30
 
19
31
  private
data/lib/sass.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # The Sass module
3
+ # The Sass module. This module will communicate with Embedded Dart Sass using
4
+ # the Embedded Sass protocol.
4
5
  module Sass
5
6
  class << self
6
7
  # The global include_paths for Sass files. This is meant for plugins and
@@ -29,12 +30,14 @@ module Sass
29
30
  embedded.info
30
31
  end
31
32
 
32
- # The global render methods. This method automatically instantiates a
33
+ # The global render method. This method automatically instantiates a
33
34
  # global {Sass::Embedded} instance when invoked the first time and call
34
- # `:render` method on the instance thereafter. The global {Sass::Embedded}
35
- # is automatically closed via {Kernel.at_exit}.
35
+ # `:render` method on the instance thereafter. See {Sass::Embedded#render}
36
+ # for supported options.
36
37
  # @example
37
- # Sass.render(options)
38
+ # Sass.render(data: 'h1 { font-size: 40px; }')
39
+ # @example
40
+ # Sass.render(file: 'style.css')
38
41
  # @return [Hash]
39
42
  def render(**kwargs)
40
43
  embedded.render(**kwargs)
@@ -55,5 +58,7 @@ require_relative 'sass/error'
55
58
  require_relative 'sass/platform'
56
59
  require_relative 'sass/util'
57
60
  require_relative 'sass/transport'
58
- require_relative 'sass/context'
61
+ require_relative 'sass/observer'
62
+ require_relative 'sass/info'
63
+ require_relative 'sass/render'
59
64
  require_relative 'sass/embedded'
data/lib/sass/embedded.rb CHANGED
@@ -1,10 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
3
  require 'base64'
4
+ require 'json'
5
5
 
6
6
  module Sass
7
- # The interface for using dart-sass-embedded
7
+ # The {Embedded} user interface for using dart-sass-embedded. Each instance
8
+ # will create its own {Transport}.
9
+ # @example
10
+ # embedded = Sass::Embedded.new
11
+ # result = embedded.render(data: 'h1 { font-size: 40px; }')
12
+ # result = embedded.render(file: 'style.css')
13
+ # embedded.close
8
14
  class Embedded
9
15
  def initialize
10
16
  @transport = Transport.new
@@ -13,7 +19,7 @@ module Sass
13
19
  end
14
20
 
15
21
  def info
16
- @info ||= InfoContext.new(@transport, next_id).fetch
22
+ @info ||= Info.new(@transport, next_id).fetch
17
23
  end
18
24
 
19
25
  def render(data: nil,
@@ -40,41 +46,41 @@ module Sass
40
46
  indent_width = parse_indent_width(indent_width)
41
47
  linefeed = parse_linefeed(linefeed)
42
48
 
43
- response = RenderContext.new(@transport, next_id,
44
- data: data,
45
- file: file,
46
- indented_syntax: indented_syntax,
47
- include_paths: include_paths,
48
- output_style: output_style,
49
- source_map: source_map,
50
- out_file: out_file,
51
- functions: functions,
52
- importer: importer).fetch
53
-
54
- if response.failure
49
+ result = Render.new(@transport, next_id,
50
+ data: data,
51
+ file: file,
52
+ indented_syntax: indented_syntax,
53
+ include_paths: include_paths,
54
+ output_style: output_style,
55
+ source_map: source_map,
56
+ out_file: out_file,
57
+ functions: functions,
58
+ importer: importer).fetch
59
+
60
+ if result.failure
55
61
  raise RenderError.new(
56
- response.failure.message,
57
- response.failure.formatted,
58
- if response.failure.span.nil?
62
+ result.failure.message,
63
+ result.failure.formatted,
64
+ if result.failure.span.nil?
59
65
  nil
60
- elsif response.failure.span.url == ''
66
+ elsif result.failure.span.url == ''
61
67
  'stdin'
62
68
  else
63
- Util.path(response.failure.span.url)
69
+ Util.path(result.failure.span.url)
64
70
  end,
65
- response.failure.span ? response.failure.span.start.line + 1 : nil,
66
- response.failure.span ? response.failure.span.start.column + 1 : nil,
71
+ result.failure.span ? result.failure.span.start.line + 1 : nil,
72
+ result.failure.span ? result.failure.span.start.column + 1 : nil,
67
73
  1
68
74
  )
69
75
  end
70
76
 
71
- map, source_map = post_process_map(map: response.success.source_map,
77
+ map, source_map = post_process_map(map: result.success.source_map,
72
78
  file: file,
73
79
  out_file: out_file,
74
80
  source_map: source_map,
75
81
  source_map_root: source_map_root)
76
82
 
77
- css = post_process_css(css: response.success.css,
83
+ css = post_process_css(css: result.success.css,
78
84
  indent_type: indent_type,
79
85
  indent_width: indent_width,
80
86
  linefeed: linefeed,
@@ -186,7 +192,7 @@ module Sass
186
192
  when :tab
187
193
  "\t"
188
194
  else
189
- raise ArgumentError, 'indent_type must be :space or :tab'
195
+ raise ArgumentError, 'indent_type must be one of :space, :tab'
190
196
  end
191
197
  end
192
198
 
@@ -219,276 +225,5 @@ module Sass
219
225
  @id
220
226
  end
221
227
  end
222
-
223
- # InfoContext
224
- class InfoContext < Context
225
- def initialize(transport, id)
226
- super(transport, id)
227
- @transport.send EmbeddedProtocol::InboundMessage::VersionRequest.new(id: @id)
228
- end
229
-
230
- def update(error, message)
231
- raise error unless error.nil?
232
-
233
- response = message[message.message.to_s]
234
-
235
- case response
236
- when EmbeddedProtocol::ProtocolError
237
- raise ProtocolError, response.message
238
- when EmbeddedProtocol::OutboundMessage::VersionResponse
239
- return unless response.id == @id
240
-
241
- Thread.new do
242
- super(nil, response)
243
- end
244
- end
245
- rescue StandardError => e
246
- Thread.new do
247
- super(e, nil)
248
- end
249
- end
250
- end
251
-
252
- # RenderContext
253
- class RenderContext < Context
254
- def initialize(transport, id,
255
- data:,
256
- file:,
257
- indented_syntax:,
258
- include_paths:,
259
- output_style:,
260
- source_map:,
261
- out_file:,
262
- functions:,
263
- importer:)
264
- raise ArgumentError, 'either data or file must be set' if file.nil? && data.nil?
265
-
266
- super(transport, id)
267
-
268
- @data = data
269
- @file = file
270
- @indented_syntax = indented_syntax
271
- @include_paths = include_paths
272
- @output_style = output_style
273
- @source_map = source_map
274
- @out_file = out_file
275
- @global_functions = functions.keys
276
- @functions = functions.transform_keys do |key|
277
- key.to_s.split('(')[0].chomp
278
- end
279
- @importer = importer
280
- @import_responses = {}
281
-
282
- @transport.send compile_request
283
- end
284
-
285
- def update(error, message)
286
- raise error unless error.nil?
287
-
288
- response = message[message.message.to_s]
289
-
290
- case response
291
- when EmbeddedProtocol::ProtocolError
292
- raise ProtocolError, response.message
293
- when EmbeddedProtocol::OutboundMessage::CompileResponse
294
- return unless response.id == @id
295
-
296
- Thread.new do
297
- super(nil, response)
298
- end
299
- when EmbeddedProtocol::OutboundMessage::LogEvent
300
- # not implemented yet
301
- when EmbeddedProtocol::OutboundMessage::CanonicalizeRequest
302
- return unless response['compilation_id'] == @id
303
-
304
- Thread.new do
305
- @transport.send canonicalize_response(response)
306
- end
307
- when EmbeddedProtocol::OutboundMessage::ImportRequest
308
- return unless response['compilation_id'] == @id
309
-
310
- Thread.new do
311
- @transport.send import_response(response)
312
- end
313
- when EmbeddedProtocol::OutboundMessage::FileImportRequest
314
- raise NotImplementedError, 'FileImportRequest is not implemented'
315
- when EmbeddedProtocol::OutboundMessage::FunctionCallRequest
316
- return unless response['compilation_id'] == @id
317
-
318
- Thread.new do
319
- @transport.send function_call_response(response)
320
- end
321
- end
322
- rescue StandardError => e
323
- Thread.new do
324
- super(e, nil)
325
- end
326
- end
327
-
328
- private
329
-
330
- def compile_request
331
- EmbeddedProtocol::InboundMessage::CompileRequest.new(
332
- id: @id,
333
- string: string,
334
- path: path,
335
- style: style,
336
- source_map: source_map,
337
- importers: importers,
338
- global_functions: global_functions,
339
- alert_color: $stderr.tty?,
340
- alert_ascii: Platform::OS == 'windows'
341
- )
342
- end
343
-
344
- def canonicalize_response(canonicalize_request)
345
- url = Util.file_uri(File.absolute_path(canonicalize_request.url, (@file.nil? ? 'stdin' : @file)))
346
-
347
- begin
348
- result = @importer[canonicalize_request.importer_id].call canonicalize_request.url, @file
349
- raise result if result.is_a? StandardError
350
- rescue StandardError => e
351
- return EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
352
- id: canonicalize_request.id,
353
- error: e.message
354
- )
355
- end
356
-
357
- if result&.key? :contents
358
- @import_responses[url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
359
- id: canonicalize_request.id,
360
- success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
361
- contents: result[:contents],
362
- syntax: EmbeddedProtocol::Syntax::SCSS,
363
- source_map_url: nil
364
- )
365
- )
366
- EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
367
- id: canonicalize_request.id,
368
- url: url
369
- )
370
- elsif result&.key? :file
371
- canonicalized_url = Util.file_uri(result[:file])
372
-
373
- # TODO: FileImportRequest is not supported yet.
374
- # Workaround by reading contents and return it when server asks
375
- @import_responses[canonicalized_url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
376
- id: canonicalize_request.id,
377
- success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
378
- contents: File.read(result[:file]),
379
- syntax: EmbeddedProtocol::Syntax::SCSS,
380
- source_map_url: nil
381
- )
382
- )
383
-
384
- EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
385
- id: canonicalize_request.id,
386
- url: canonicalized_url
387
- )
388
- else
389
- EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
390
- id: canonicalize_request.id
391
- )
392
- end
393
- end
394
-
395
- def import_response(import_request)
396
- url = import_request.url
397
-
398
- if @import_responses.key? url
399
- @import_responses[url].id = import_request.id
400
- else
401
- @import_responses[url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
402
- id: import_request.id,
403
- error: "Failed to import: #{url}"
404
- )
405
- end
406
-
407
- @import_responses[url]
408
- end
409
-
410
- def function_call_response(function_call_request)
411
- EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
412
- id: function_call_request.id,
413
- success: @functions[function_call_request.name].call(*function_call_request.arguments)
414
- )
415
- rescue StandardError => e
416
- EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
417
- id: function_call_request.id,
418
- error: e.message
419
- )
420
- end
421
-
422
- def syntax
423
- if @indented_syntax == true
424
- EmbeddedProtocol::Syntax::INDENTED
425
- else
426
- EmbeddedProtocol::Syntax::SCSS
427
- end
428
- end
429
-
430
- def url
431
- return if @file.nil?
432
-
433
- Util.file_uri @file
434
- end
435
-
436
- def string
437
- return if @data.nil?
438
-
439
- EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
440
- source: @data,
441
- url: url,
442
- syntax: syntax
443
- )
444
- end
445
-
446
- def path
447
- @file if @data.nil?
448
- end
449
-
450
- def style
451
- case @output_style&.to_sym
452
- when :expanded
453
- EmbeddedProtocol::OutputStyle::EXPANDED
454
- when :compressed
455
- EmbeddedProtocol::OutputStyle::COMPRESSED
456
- when :nested, :compact
457
- raise ArgumentError, "#{@output_style} is not a supported output_style"
458
- else
459
- raise ArgumentError, "#{@output_style} is not a valid utput_style"
460
- end
461
- end
462
-
463
- def source_map
464
- @source_map.is_a?(String) || (@source_map == true && !@out_file.nil?)
465
- end
466
-
467
- attr_reader :global_functions
468
-
469
- # Order
470
- # 1. Loading a file relative to the file in which the @use or @import appeared.
471
- # 2. Each custom importer.
472
- # 3. Loading a file relative to the current working directory.
473
- # 4. Each load path in includePaths
474
- # 5. Each load path specified in the SASS_PATH environment variable, which should be semicolon-separated on Windows and colon-separated elsewhere.
475
- def importers
476
- custom_importers = @importer.map.with_index do |_, id|
477
- EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
478
- importer_id: id
479
- )
480
- end
481
-
482
- include_path_importers = @include_paths
483
- .concat(Sass.include_paths)
484
- .map do |include_path|
485
- EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
486
- path: File.absolute_path(include_path)
487
- )
488
- end
489
-
490
- custom_importers.concat include_path_importers
491
- end
492
- end
493
228
  end
494
229
  end
data/lib/sass/error.rb CHANGED
@@ -5,7 +5,7 @@ module Sass
5
5
 
6
6
  class ProtocolError < SassError; end
7
7
 
8
- # The error returned by {Sass.render}
8
+ # The error returned by {Embedded#render}.
9
9
  class RenderError < SassError
10
10
  attr_accessor :formatted, :file, :line, :column, :status
11
11
 
data/lib/sass/info.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # The {Observer} for {Embedded#info}.
5
+ class Info < Observer
6
+ def initialize(transport, id)
7
+ super(transport, id)
8
+ @transport.send EmbeddedProtocol::InboundMessage::VersionRequest.new(id: @id)
9
+ end
10
+
11
+ def update(error, message)
12
+ raise error unless error.nil?
13
+
14
+ case message
15
+ when EmbeddedProtocol::ProtocolError
16
+ raise ProtocolError, message.message
17
+ when EmbeddedProtocol::OutboundMessage::VersionResponse
18
+ return unless message.id == @id
19
+
20
+ Thread.new do
21
+ super(nil, message)
22
+ end
23
+ end
24
+ rescue StandardError => e
25
+ Thread.new do
26
+ super(e, nil)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,35 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sass
4
- # An abstract context for maintaining state and observing transport
5
- class Context
4
+ # The abstract {Observer} for tracking state and observing messages
5
+ # from {Transport}.
6
+ class Observer
6
7
  def initialize(transport, id)
7
- raise NotImplementedError if instance_of? Context
8
+ raise NotImplementedError if instance_of? Observer
8
9
 
9
10
  @transport = transport
10
11
  @id = id
11
12
  @mutex = Mutex.new
12
13
  @condition_variable = ConditionVariable.new
13
- @response = nil
14
14
  @error = nil
15
+ @message = nil
15
16
  @transport.add_observer self
16
17
  end
17
18
 
18
19
  def fetch
19
20
  @mutex.synchronize do
20
- @condition_variable.wait(@mutex) if @error.nil? && @response.nil?
21
+ @condition_variable.wait(@mutex) if @error.nil? && @message.nil?
21
22
  end
22
23
 
23
24
  raise @error unless @error.nil?
24
25
 
25
- @response
26
+ @message
26
27
  end
27
28
 
28
29
  def update(error, message)
29
30
  @transport.delete_observer self
30
31
  @mutex.synchronize do
31
32
  @error = error
32
- @response = message
33
+ @message = message
33
34
  @condition_variable.broadcast
34
35
  end
35
36
  end
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # The {Observer} for {Embedded#render}.
5
+ class Render < Observer
6
+ def initialize(transport, id,
7
+ data:,
8
+ file:,
9
+ indented_syntax:,
10
+ include_paths:,
11
+ output_style:,
12
+ source_map:,
13
+ out_file:,
14
+ functions:,
15
+ importer:)
16
+ raise ArgumentError, 'either data or file must be set' if file.nil? && data.nil?
17
+
18
+ super(transport, id)
19
+
20
+ @data = data
21
+ @file = file
22
+ @indented_syntax = indented_syntax
23
+ @include_paths = include_paths
24
+ @output_style = output_style
25
+ @source_map = source_map
26
+ @out_file = out_file
27
+ @global_functions = functions.keys
28
+ @functions = functions.transform_keys do |key|
29
+ key.to_s.split('(')[0].chomp
30
+ end
31
+ @importer = importer
32
+ @import_responses = {}
33
+
34
+ @transport.send compile_request
35
+ end
36
+
37
+ def update(error, message)
38
+ raise error unless error.nil?
39
+
40
+ case message
41
+ when EmbeddedProtocol::ProtocolError
42
+ raise ProtocolError, message.message
43
+ when EmbeddedProtocol::OutboundMessage::CompileResponse
44
+ return unless message.id == @id
45
+
46
+ Thread.new do
47
+ super(nil, message)
48
+ end
49
+ when EmbeddedProtocol::OutboundMessage::LogEvent
50
+ # not implemented yet
51
+ when EmbeddedProtocol::OutboundMessage::CanonicalizeRequest
52
+ return unless message['compilation_id'] == @id
53
+
54
+ Thread.new do
55
+ @transport.send canonicalize_response message
56
+ end
57
+ when EmbeddedProtocol::OutboundMessage::ImportRequest
58
+ return unless message['compilation_id'] == @id
59
+
60
+ Thread.new do
61
+ @transport.send import_response message
62
+ end
63
+ when EmbeddedProtocol::OutboundMessage::FileImportRequest
64
+ raise NotImplementedError, 'FileImportRequest is not implemented'
65
+ when EmbeddedProtocol::OutboundMessage::FunctionCallRequest
66
+ return unless message['compilation_id'] == @id
67
+
68
+ Thread.new do
69
+ @transport.send function_call_response message
70
+ end
71
+ end
72
+ rescue StandardError => e
73
+ Thread.new do
74
+ super(e, nil)
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def compile_request
81
+ EmbeddedProtocol::InboundMessage::CompileRequest.new(
82
+ id: @id,
83
+ string: string,
84
+ path: path,
85
+ style: style,
86
+ source_map: source_map,
87
+ importers: importers,
88
+ global_functions: global_functions,
89
+ alert_color: $stderr.tty?,
90
+ alert_ascii: Platform::OS == 'windows'
91
+ )
92
+ end
93
+
94
+ def canonicalize_response(canonicalize_request)
95
+ url = Util.file_uri(File.absolute_path(canonicalize_request.url, (@file.nil? ? 'stdin' : @file)))
96
+
97
+ begin
98
+ result = @importer[canonicalize_request.importer_id].call canonicalize_request.url, @file
99
+ raise result if result.is_a? StandardError
100
+ rescue StandardError => e
101
+ return EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
102
+ id: canonicalize_request.id,
103
+ error: e.message
104
+ )
105
+ end
106
+
107
+ if result&.key? :contents
108
+ @import_responses[url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
109
+ id: canonicalize_request.id,
110
+ success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
111
+ contents: result[:contents],
112
+ syntax: EmbeddedProtocol::Syntax::SCSS,
113
+ source_map_url: nil
114
+ )
115
+ )
116
+ EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
117
+ id: canonicalize_request.id,
118
+ url: url
119
+ )
120
+ elsif result&.key? :file
121
+ canonicalized_url = Util.file_uri(result[:file])
122
+
123
+ # TODO: FileImportRequest is not supported yet.
124
+ # Workaround by reading contents and return it when server asks
125
+ @import_responses[canonicalized_url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
126
+ id: canonicalize_request.id,
127
+ success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
128
+ contents: File.read(result[:file]),
129
+ syntax: EmbeddedProtocol::Syntax::SCSS,
130
+ source_map_url: nil
131
+ )
132
+ )
133
+
134
+ EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
135
+ id: canonicalize_request.id,
136
+ url: canonicalized_url
137
+ )
138
+ else
139
+ EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
140
+ id: canonicalize_request.id
141
+ )
142
+ end
143
+ end
144
+
145
+ def import_response(import_request)
146
+ url = import_request.url
147
+
148
+ if @import_responses.key? url
149
+ @import_responses[url].id = import_request.id
150
+ else
151
+ @import_responses[url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
152
+ id: import_request.id,
153
+ error: "Failed to import: #{url}"
154
+ )
155
+ end
156
+
157
+ @import_responses[url]
158
+ end
159
+
160
+ def function_call_response(function_call_request)
161
+ EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
162
+ id: function_call_request.id,
163
+ success: @functions[function_call_request.name].call(*function_call_request.arguments)
164
+ )
165
+ rescue StandardError => e
166
+ EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
167
+ id: function_call_request.id,
168
+ error: e.message
169
+ )
170
+ end
171
+
172
+ def syntax
173
+ if @indented_syntax == true
174
+ EmbeddedProtocol::Syntax::INDENTED
175
+ else
176
+ EmbeddedProtocol::Syntax::SCSS
177
+ end
178
+ end
179
+
180
+ def url
181
+ return if @file.nil?
182
+
183
+ Util.file_uri @file
184
+ end
185
+
186
+ def string
187
+ return if @data.nil?
188
+
189
+ EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
190
+ source: @data,
191
+ url: url,
192
+ syntax: syntax
193
+ )
194
+ end
195
+
196
+ def path
197
+ @file if @data.nil?
198
+ end
199
+
200
+ def style
201
+ case @output_style&.to_sym
202
+ when :expanded
203
+ EmbeddedProtocol::OutputStyle::EXPANDED
204
+ when :compressed
205
+ EmbeddedProtocol::OutputStyle::COMPRESSED
206
+ else
207
+ raise ArgumentError, 'output_style must be one of :expanded, :compressed'
208
+ end
209
+ end
210
+
211
+ def source_map
212
+ @source_map.is_a?(String) || (@source_map == true && !@out_file.nil?)
213
+ end
214
+
215
+ attr_reader :global_functions
216
+
217
+ # Order
218
+ # 1. Loading a file relative to the file in which the @use or @import appeared.
219
+ # 2. Each custom importer.
220
+ # 3. Loading a file relative to the current working directory.
221
+ # 4. Each load path in includePaths
222
+ # 5. Each load path specified in the SASS_PATH environment variable, which should
223
+ # be semicolon-separated on Windows and colon-separated elsewhere.
224
+ def importers
225
+ custom_importers = @importer.map.with_index do |_, id|
226
+ EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
227
+ importer_id: id
228
+ )
229
+ end
230
+
231
+ include_path_importers = @include_paths
232
+ .concat(Sass.include_paths)
233
+ .map do |include_path|
234
+ EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
235
+ path: File.absolute_path(include_path)
236
+ )
237
+ end
238
+
239
+ custom_importers.concat include_path_importers
240
+ end
241
+ end
242
+ end
@@ -5,9 +5,9 @@ require 'observer'
5
5
  require_relative '../../ext/embedded_sass_pb'
6
6
 
7
7
  module Sass
8
- # The interface for communicating with dart-sass-embedded.
9
- # It handles message serialization and deserialization as well as
10
- # tracking concurrent request and response
8
+ # The {Observable} {Transport} for low level communication with
9
+ # dart-sass-embedded using protocol buffers via stdio. Received messages
10
+ # can be observed by an {Observer}.
11
11
  class Transport
12
12
  include Observable
13
13
 
@@ -17,24 +17,31 @@ module Sass
17
17
 
18
18
  PROTOCOL_ERROR_ID = 4_294_967_295
19
19
 
20
+ ONEOF_MESSAGE = EmbeddedProtocol::InboundMessage
21
+ .descriptor
22
+ .lookup_oneof('message')
23
+ .collect do |field_descriptor|
24
+ [field_descriptor.subtype, field_descriptor.name]
25
+ end.to_h
26
+
20
27
  def initialize
21
- @stdin_semaphore = Mutex.new
22
- @observerable_semaphore = Mutex.new
28
+ @observerable_mutex = Mutex.new
29
+ @stdin_mutex = Mutex.new
23
30
  @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(DART_SASS_EMBEDDED)
24
31
  pipe @stderr, $stderr
25
32
  receive
26
33
  end
27
34
 
28
35
  def add_observer(*args)
29
- @observerable_semaphore.synchronize do
36
+ @observerable_mutex.synchronize do
30
37
  super(*args)
31
38
  end
32
39
  end
33
40
 
34
- def send(req)
35
- req_kind = req.class.name.split('::').last.gsub(/\B(?=[A-Z])/, '_').downcase
36
- message = EmbeddedProtocol::InboundMessage.new(req_kind => req)
37
- write message.to_proto
41
+ def send(message)
42
+ write EmbeddedProtocol::InboundMessage.new(
43
+ ONEOF_MESSAGE[message.class.descriptor] => message
44
+ ).to_proto
38
45
  end
39
46
 
40
47
  def close
@@ -63,9 +70,9 @@ module Sass
63
70
  end
64
71
  payload = @stdout.read length
65
72
  message = EmbeddedProtocol::OutboundMessage.decode payload
66
- @observerable_semaphore.synchronize do
73
+ @observerable_mutex.synchronize do
67
74
  changed
68
- notify_observers nil, message
75
+ notify_observers nil, message[message.message.to_s]
69
76
  end
70
77
  rescue Interrupt
71
78
  break
@@ -84,7 +91,7 @@ module Sass
84
91
  rescue Interrupt
85
92
  break
86
93
  rescue IOError => e
87
- @observerable_semaphore.synchronize do
94
+ @observerable_mutex.synchronize do
88
95
  notify_observers e, nil
89
96
  end
90
97
  close
@@ -94,7 +101,7 @@ module Sass
94
101
  end
95
102
 
96
103
  def write(payload)
97
- @stdin_semaphore.synchronize do
104
+ @stdin_mutex.synchronize do
98
105
  length = payload.length
99
106
  while length.positive?
100
107
  @stdin.write ((length > 0x7f ? 0x80 : 0) | (length & 0x7f)).chr
data/lib/sass/util.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require 'pathname'
4
4
 
5
5
  module Sass
6
- # Utilities functions
6
+ # The utilitiy module.
7
7
  module Util
8
8
  module_function
9
9
 
data/lib/sass/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sass
4
- VERSION = '0.4.2'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -12,6 +12,8 @@ module Sass
12
12
  @embedded.close
13
13
  end
14
14
 
15
+ # rubocop:disable Layout/LineLength
16
+
15
17
  def render(sass)
16
18
  @embedded.render(data: sass,
17
19
  functions: {
@@ -163,6 +165,8 @@ module Sass
163
165
  })[:css]
164
166
  end
165
167
 
168
+ # rubocop:enable Layout/LineLength
169
+
166
170
  def test_functions_may_return_sass_string_type
167
171
  assert_sass <<-SCSS, <<-CSS
168
172
  div { url: url(sass_return_path("foo.svg")); }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sass-embedded
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - なつき
@@ -155,10 +155,12 @@ files:
155
155
  - ext/Makefile
156
156
  - ext/extconf.rb
157
157
  - lib/sass.rb
158
- - lib/sass/context.rb
159
158
  - lib/sass/embedded.rb
160
159
  - lib/sass/error.rb
160
+ - lib/sass/info.rb
161
+ - lib/sass/observer.rb
161
162
  - lib/sass/platform.rb
163
+ - lib/sass/render.rb
162
164
  - lib/sass/transport.rb
163
165
  - lib/sass/util.rb
164
166
  - lib/sass/version.rb