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 +4 -4
- data/.rubocop.yml +0 -3
- data/ext/extconf.rb +14 -2
- data/lib/sass.rb +11 -6
- data/lib/sass/embedded.rb +31 -296
- data/lib/sass/error.rb +1 -1
- data/lib/sass/info.rb +30 -0
- data/lib/sass/{context.rb → observer.rb} +8 -7
- data/lib/sass/render.rb +242 -0
- data/lib/sass/transport.rb +21 -14
- data/lib/sass/util.rb +1 -1
- data/lib/sass/version.rb +1 -1
- data/test/functions_test.rb +4 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1086f5483670120b40e9741187e5150a5af9e9ad5d38159d6d57459edec350b
|
4
|
+
data.tar.gz: d6afead31f102a04ad4d11c82e1381b597c285ae0206564640763173e2b67fc6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8c534da41310d6b7e6228624d26a6e3dd94bccc4b67b2e9a25bff82251cb3b4a5d312c58ec1e498f944731cc99f4fdcb66b117278640cee2fc3d81f95924f66
|
7
|
+
data.tar.gz: 6d8808fc65dbb7fc4b43976beb6c8573635d874e9d72a7949f20eda5406e582ea0933a99f844256ec284e3ecf1e8e6f02b967f50854fde661470bf6834550abb
|
data/.rubocop.yml
CHANGED
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
|
-
#
|
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
|
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.
|
35
|
-
#
|
35
|
+
# `:render` method on the instance thereafter. See {Sass::Embedded#render}
|
36
|
+
# for supported options.
|
36
37
|
# @example
|
37
|
-
# Sass.render(
|
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/
|
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 ||=
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
if
|
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
|
-
|
57
|
-
|
58
|
-
if
|
62
|
+
result.failure.message,
|
63
|
+
result.failure.formatted,
|
64
|
+
if result.failure.span.nil?
|
59
65
|
nil
|
60
|
-
elsif
|
66
|
+
elsif result.failure.span.url == ''
|
61
67
|
'stdin'
|
62
68
|
else
|
63
|
-
Util.path(
|
69
|
+
Util.path(result.failure.span.url)
|
64
70
|
end,
|
65
|
-
|
66
|
-
|
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:
|
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:
|
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
|
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
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
|
-
#
|
5
|
-
|
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?
|
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? && @
|
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
|
-
@
|
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
|
-
@
|
33
|
+
@message = message
|
33
34
|
@condition_variable.broadcast
|
34
35
|
end
|
35
36
|
end
|
data/lib/sass/render.rb
ADDED
@@ -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
|
data/lib/sass/transport.rb
CHANGED
@@ -5,9 +5,9 @@ require 'observer'
|
|
5
5
|
require_relative '../../ext/embedded_sass_pb'
|
6
6
|
|
7
7
|
module Sass
|
8
|
-
# The
|
9
|
-
#
|
10
|
-
#
|
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
|
-
@
|
22
|
-
@
|
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
|
-
@
|
36
|
+
@observerable_mutex.synchronize do
|
30
37
|
super(*args)
|
31
38
|
end
|
32
39
|
end
|
33
40
|
|
34
|
-
def send(
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
data/lib/sass/version.rb
CHANGED
data/test/functions_test.rb
CHANGED
@@ -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
|
+
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
|