sass-embedded 0.18.2 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sass/compile_error.rb +1 -1
  3. data/lib/sass/embedded/async.rb +65 -0
  4. data/lib/sass/embedded/channel.rb +21 -22
  5. data/lib/sass/embedded/compiler.rb +17 -83
  6. data/lib/sass/embedded/dispatcher.rb +90 -0
  7. data/lib/sass/embedded/{compile_context → host}/function_registry.rb +6 -4
  8. data/lib/sass/embedded/{compile_context → host}/importer_registry.rb +13 -9
  9. data/lib/sass/embedded/{compile_context → host}/logger_registry.rb +14 -9
  10. data/lib/sass/embedded/{compile_context → host}/value_protofier.rb +6 -4
  11. data/lib/sass/embedded/host.rb +135 -0
  12. data/lib/sass/embedded/legacy.rb +0 -2
  13. data/lib/sass/embedded/protofier.rb +3 -21
  14. data/lib/sass/embedded/structifier.rb +3 -1
  15. data/lib/sass/embedded/varint.rb +3 -1
  16. data/lib/sass/embedded/version.rb +1 -1
  17. data/lib/sass/embedded.rb +37 -39
  18. data/lib/sass/value/argument_list.rb +2 -2
  19. data/lib/sass/value/boolean.rb +5 -3
  20. data/lib/sass/value/color.rb +12 -5
  21. data/lib/sass/value/function.rb +5 -3
  22. data/lib/sass/value/fuzzy_math.rb +1 -1
  23. data/lib/sass/value/list.rb +5 -3
  24. data/lib/sass/value/map.rb +5 -3
  25. data/lib/sass/value/null.rb +5 -3
  26. data/lib/sass/value/number/unit.rb +1 -1
  27. data/lib/sass/value/number.rb +5 -3
  28. data/lib/sass/value/string.rb +6 -5
  29. data/lib/sass/value.rb +2 -3
  30. data/lib/sass.rb +8 -13
  31. metadata +11 -12
  32. data/lib/sass/embedded/compile_context.rb +0 -107
  33. data/lib/sass/embedded/observer.rb +0 -49
  34. data/lib/sass/embedded/protocol_error.rb +0 -7
  35. data/lib/sass/embedded/version_context.rb +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9eeafb6dcad576b707936df8fe78e0de78712c7a1fb51c0adc372a83721d371
4
- data.tar.gz: 5f1a354a8c9b08f37f6215c9d9a53582b0eef7e48f62c84d910671ae960b2677
3
+ metadata.gz: 573a157ee6e0c3fa51cefec5c011649717718133d934e031a339d32a84838a13
4
+ data.tar.gz: ef27dd3177b8f58f3dd19180340686901597e9ac05efa77e2f9cc12b7784dbbb
5
5
  SHA512:
6
- metadata.gz: 50ac1de6a47ac9becebe4f229ffd6befbc25a0a481a4551e88fc600ff8934f554ab1ecc35bee8e68847d5d2315058d9a21a4952cd4ee86b3ccf011c21ec62ed9
7
- data.tar.gz: daa53389ab4d49e5d8a6189eaf4391a978a640444b79279b401beac6070086db4903786b4df9d6ee9a0216a4391e3f4cb32a9be2c1c25c4ec9eae3a145f065c1
6
+ metadata.gz: 538de1728de2da841ce66648d669acec38a4a3c3f15357e5d33e1d5dac773f246b43f29d244ec39076fc3d18504f6935374f9a5277baac7a1c400548b44aef5b
7
+ data.tar.gz: 26db551dd98592327a0c7e4fc9ee39c5917b1aff92431bfc7cdac6eea3c79899439c976512c7bed393493f564fd00e0ccafe735fe8cd8a514827d7a42b689d58
@@ -12,7 +12,7 @@ module Sass
12
12
  @span = span
13
13
  end
14
14
 
15
- def full_message(*_args, **_kwargs)
15
+ def full_message(*)
16
16
  if @full_message.nil?
17
17
  super
18
18
  else
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ class Embedded
5
+ # The {Async} class.
6
+ #
7
+ # It awaits until the promise is resolved or rejected.
8
+ class Async
9
+ module State
10
+ PENDING = 0
11
+ FULFILLED = 1
12
+ REJECTED = 2
13
+ end
14
+
15
+ private_constant :State
16
+
17
+ def initialize
18
+ @error = nil
19
+ @result = nil
20
+ @state = State::PENDING
21
+
22
+ @condition_variable = ConditionVariable.new
23
+ @mutex = Mutex.new
24
+
25
+ begin
26
+ yield if block_given?
27
+ rescue StandardError => e
28
+ reject e
29
+ end
30
+ end
31
+
32
+ def resolve(value)
33
+ @mutex.synchronize do
34
+ return unless @state == State::PENDING
35
+
36
+ @state = State::FULFILLED
37
+ @result = value
38
+ @condition_variable.broadcast
39
+ end
40
+ end
41
+
42
+ def reject(reason)
43
+ @mutex.synchronize do
44
+ return unless @state == State::PENDING
45
+
46
+ @state = State::REJECTED
47
+ @error = reason
48
+ @condition_variable.broadcast
49
+ end
50
+ end
51
+
52
+ def await
53
+ @mutex.synchronize do
54
+ @condition_variable.wait(@mutex) if @state == State::PENDING
55
+
56
+ raise @error if @state == State::REJECTED
57
+
58
+ @result
59
+ end
60
+ end
61
+ end
62
+
63
+ private_constant :Async
64
+ end
65
+ end
@@ -2,59 +2,58 @@
2
2
 
3
3
  module Sass
4
4
  class Embedded
5
- # The {Channel} for {Compiler} calls. Each instance creates its own
6
- # {Compiler}. A new {Compiler} is automatically created when the existing
7
- # {Compiler} runs out of unique request id.
5
+ # The {Channel} class.
6
+ #
7
+ # It establishes connection between {Host} and {Dispatcher}.
8
8
  class Channel
9
9
  def initialize
10
10
  @mutex = Mutex.new
11
- @compiler = Compiler.new
11
+ @dispatcher = Dispatcher.new
12
12
  end
13
13
 
14
14
  def close
15
15
  @mutex.synchronize do
16
- @compiler.close
16
+ @dispatcher.close
17
17
  end
18
18
  end
19
19
 
20
20
  def closed?
21
21
  @mutex.synchronize do
22
- @compiler.closed?
22
+ @dispatcher.closed?
23
23
  end
24
24
  end
25
25
 
26
- def subscribe(observer)
26
+ def connect(observer)
27
27
  @mutex.synchronize do
28
28
  begin
29
- id = @compiler.add_observer(observer)
30
- rescue ProtocolError
31
- @compiler = Compiler.new
32
- id = @compiler.add_observer(observer)
29
+ id = @dispatcher.subscribe(observer)
30
+ rescue EOFError
31
+ @dispatcher = Dispatcher.new
32
+ id = @dispatcher.subscribe(observer)
33
33
  end
34
- Subscription.new @compiler, observer, id
34
+ Connection.new(@dispatcher, id)
35
35
  end
36
36
  end
37
37
 
38
- # The {Subscription} between {Compiler} and {Observer}.
39
- class Subscription
38
+ # The {Connection} between {Host} to {Dispatcher}.
39
+ class Connection
40
40
  attr_reader :id
41
41
 
42
- def initialize(compiler, observer, id)
43
- @compiler = compiler
44
- @observer = observer
42
+ def initialize(dispatcher, id)
43
+ @dispatcher = dispatcher
45
44
  @id = id
46
45
  end
47
46
 
48
- def unsubscribe
49
- @compiler.delete_observer(@observer)
47
+ def disconnect
48
+ @dispatcher.unsubscribe(id)
50
49
  end
51
50
 
52
- def send_message(*args)
53
- @compiler.send_message(*args)
51
+ def send_message(message)
52
+ @dispatcher.send_message(message)
54
53
  end
55
54
  end
56
55
 
57
- private_constant :Subscription
56
+ private_constant :Connection
58
57
  end
59
58
 
60
59
  private_constant :Channel
@@ -1,65 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'observer'
4
3
  require 'open3'
5
4
 
6
5
  module Sass
7
6
  class Embedded
8
- # The {::Observable} {Compiler} for low level communication with
9
- # `dart-sass-embedded` using protocol buffers via stdio. Received messages
10
- # can be observed by an {Observer}.
7
+ # The {Compiler} class.
8
+ #
9
+ # It runs the `dart-sass-embedded` process.
11
10
  class Compiler
12
- include Observable
13
-
14
11
  PATH = File.absolute_path(
15
12
  "../../../ext/sass/sass_embedded/dart-sass-embedded#{Gem.win_platform? ? '.bat' : ''}", __dir__
16
13
  )
17
14
 
18
- PROTOCOL_ERROR_ID = 4_294_967_295
19
-
20
15
  def initialize
21
- @observerable_mutex = Mutex.new
22
- @id = 0
23
16
  @stdin_mutex = Mutex.new
17
+ @stdout_mutex = Mutex.new
24
18
  @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(PATH)
25
19
 
26
20
  [@stdin, @stdout].each(&:binmode)
27
21
 
28
- poll do
29
- warn(@stderr.readline, uplevel: 1)
30
- end
31
- poll do
32
- receive_message Protofier.from_proto_message read
33
- end
34
- end
35
-
36
- def add_observer(*args)
37
- @observerable_mutex.synchronize do
38
- raise ProtocolError, 'half-closed compiler' if half_closed?
39
-
40
- super(*args)
41
-
42
- id = @id
43
- @id = @id.next
44
- id
45
- end
46
- end
47
-
48
- def delete_observer(*args)
49
- @observerable_mutex.synchronize do
50
- super(*args)
51
-
52
- close if half_closed? && count_observers.zero?
22
+ Thread.new do
23
+ loop do
24
+ warn(@stderr.readline, uplevel: 1)
25
+ rescue IOError
26
+ break
27
+ end
53
28
  end
54
29
  end
55
30
 
56
- def send_message(message)
57
- write Protofier.to_proto_message message
58
- end
59
-
60
31
  def close
61
- delete_observers
62
-
63
32
  @stdin_mutex.synchronize do
64
33
  @stdin.close unless @stdin.closed?
65
34
  @stdout.close unless @stdout.closed?
@@ -75,54 +44,19 @@ module Sass
75
44
  end
76
45
  end
77
46
 
78
- private
79
-
80
- def half_closed?
81
- @id == PROTOCOL_ERROR_ID
82
- end
83
-
84
- def poll
85
- Thread.new do
86
- loop do
87
- yield
88
- rescue StandardError
89
- break
90
- end
91
- end
92
- end
93
-
94
- def notify_observers(*args)
95
- @observerable_mutex.synchronize do
96
- changed
97
- super(*args)
98
- end
99
- end
100
-
101
- def receive_message(message)
102
- case message
103
- when EmbeddedProtocol::ProtocolError
104
- notify_observers(ProtocolError.new(message.message), nil)
105
- close
106
- else
107
- notify_observers(nil, message)
108
- end
109
- end
110
-
111
- def read
112
- length = Varint.read(@stdout)
113
- @stdout.read(length)
114
- rescue IOError => e
115
- notify_observers(e, nil)
116
- close
117
- raise e
118
- end
119
-
120
47
  def write(payload)
121
48
  @stdin_mutex.synchronize do
122
49
  Varint.write(@stdin, payload.length)
123
50
  @stdin.write(payload)
124
51
  end
125
52
  end
53
+
54
+ def read
55
+ @stdout_mutex.synchronize do
56
+ length = Varint.read(@stdout)
57
+ @stdout.read(length)
58
+ end
59
+ end
126
60
  end
127
61
 
128
62
  private_constant :Compiler
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ class Embedded
5
+ # The {Dispatcher} class.
6
+ #
7
+ # It dispatches messages between mutliple instances of {Host} and a single {Compiler}.
8
+ class Dispatcher
9
+ PROTOCOL_ERROR_ID = 4_294_967_295
10
+
11
+ def initialize
12
+ @compiler = Compiler.new
13
+ @observers = {}
14
+ @id = 0
15
+ @mutex = Mutex.new
16
+
17
+ Thread.new do
18
+ loop do
19
+ receive_message EmbeddedProtocol::OutboundMessage.decode @compiler.read
20
+ rescue IOError => e
21
+ half_close
22
+ @observers.each_value do |observer|
23
+ observer.error e
24
+ end
25
+ break
26
+ end
27
+ end
28
+ end
29
+
30
+ def subscribe(observer)
31
+ @mutex.synchronize do
32
+ raise EOFError if half_closed?
33
+
34
+ id = @id
35
+ @id = id.next
36
+ @observers.store(id, observer)
37
+ id
38
+ end
39
+ end
40
+
41
+ def unsubscribe(id)
42
+ @observers.delete(id)
43
+
44
+ close if half_closed? && @observers.empty?
45
+ end
46
+
47
+ def close
48
+ @compiler.close
49
+ end
50
+
51
+ def closed?
52
+ @compiler.closed?
53
+ end
54
+
55
+ def send_message(inbound_message)
56
+ @compiler.write(inbound_message.to_proto)
57
+ end
58
+
59
+ private
60
+
61
+ def half_close
62
+ @mutex.synchronize do
63
+ @id = PROTOCOL_ERROR_ID
64
+ end
65
+ end
66
+
67
+ def half_closed?
68
+ @id == PROTOCOL_ERROR_ID
69
+ end
70
+
71
+ def receive_message(outbound_message)
72
+ message = outbound_message.send(outbound_message.message)
73
+
74
+ case outbound_message.message
75
+ when :error
76
+ half_close
77
+ @observers[message.id]&.send(outbound_message.message, message)
78
+ when :compile_response, :version_response
79
+ @observers[message.id]&.send(outbound_message.message, message)
80
+ when :log_event, :canonicalize_request, :import_request, :file_import_request, :function_call_request
81
+ @observers[message.compilation_id]&.send(outbound_message.message, message)
82
+ else
83
+ raise ArgumentError, "Unknown OutboundMessage.message #{message}"
84
+ end
85
+ end
86
+ end
87
+
88
+ private_constant :Dispatcher
89
+ end
90
+ end
@@ -2,12 +2,14 @@
2
2
 
3
3
  module Sass
4
4
  class Embedded
5
- class CompileContext
6
- # The {FunctionRegistry} for {CompileContext}.
5
+ class Host
6
+ # The {FunctionRegistry} class.
7
+ #
8
+ # It stores sass custom functions and handles function calls.
7
9
  class FunctionRegistry
8
10
  attr_reader :global_functions
9
11
 
10
- def initialize(functions, highlight:)
12
+ def initialize(functions, alert_color:)
11
13
  functions = functions.transform_keys(&:to_s)
12
14
 
13
15
  @global_functions = functions.keys
@@ -25,7 +27,7 @@ module Sass
25
27
  @functions_by_id = {}
26
28
  @ids_by_function = {}
27
29
 
28
- @highlight = highlight
30
+ @highlight = alert_color
29
31
  end
30
32
 
31
33
  def register(function)
@@ -2,23 +2,27 @@
2
2
 
3
3
  module Sass
4
4
  class Embedded
5
- class CompileContext
6
- # The {ImporterRegistry} for {CompileContext}.
5
+ class Host
6
+ # The {ImporterRegistry} class.
7
+ #
8
+ # It stores importers and handles import requests.
7
9
  class ImporterRegistry
8
10
  attr_reader :importers
9
11
 
10
- def initialize(importers, load_paths, highlight:)
12
+ def initialize(importers, load_paths, alert_color:)
11
13
  @id = 0
12
14
  @importers_by_id = {}
13
15
  @importers = importers
14
16
  .map { |importer| register(importer) }
15
- .concat(load_paths.map do |load_path|
16
- EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
17
- path: File.absolute_path(load_path)
18
- )
19
- end)
17
+ .concat(
18
+ load_paths.map do |load_path|
19
+ EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
20
+ path: File.absolute_path(load_path)
21
+ )
22
+ end
23
+ )
20
24
 
21
- @highlight = highlight
25
+ @highlight = alert_color
22
26
  end
23
27
 
24
28
  def register(importer)
@@ -2,8 +2,10 @@
2
2
 
3
3
  module Sass
4
4
  class Embedded
5
- class CompileContext
6
- # The {LoggerRegistry} for {CompileContext}.
5
+ class Host
6
+ # The {LoggerRegistry} class.
7
+ #
8
+ # It stores logger and handles log events.
7
9
  class LoggerRegistry
8
10
  attr_reader :logger
9
11
 
@@ -15,23 +17,26 @@ module Sass
15
17
  case event.type
16
18
  when :DEBUG
17
19
  if logger.respond_to? :debug
18
- logger.debug(event.message, span: Protofier.from_proto_source_span(event.span))
20
+ logger.debug(event.message,
21
+ span: Protofier.from_proto_source_span(event.span))
19
22
  else
20
23
  warn(event.formatted)
21
24
  end
22
25
  when :DEPRECATION_WARNING
23
26
  if logger.respond_to? :warn
24
- logger.warn(event.message, deprecation: true,
25
- span: Protofier.from_proto_source_span(event.span),
26
- stack: event.stack_trace)
27
+ logger.warn(event.message,
28
+ deprecation: true,
29
+ span: Protofier.from_proto_source_span(event.span),
30
+ stack: event.stack_trace)
27
31
  else
28
32
  warn(event.formatted)
29
33
  end
30
34
  when :WARNING
31
35
  if logger.respond_to? :warn
32
- logger.warn(event.message, deprecation: false,
33
- span: Protofier.from_proto_source_span(event.span),
34
- stack: event.stack_trace)
36
+ logger.warn(event.message,
37
+ deprecation: false,
38
+ span: Protofier.from_proto_source_span(event.span),
39
+ stack: event.stack_trace)
35
40
  else
36
41
  warn(event.formatted)
37
42
  end
@@ -2,8 +2,10 @@
2
2
 
3
3
  module Sass
4
4
  class Embedded
5
- class CompileContext
6
- # The {ValueProtofier} between Pure Ruby types and Protobuf Ruby types.
5
+ class Host
6
+ # The {ValueProtofier} class.
7
+ #
8
+ # It converts Pure Ruby types and Protobuf Ruby types.
7
9
  class ValueProtofier
8
10
  def initialize(function_registry)
9
11
  @function_registry = function_registry
@@ -122,8 +124,8 @@ module Sass
122
124
  when :number
123
125
  Sass::Value::Number.new(
124
126
  obj.value,
125
- obj.numerators,
126
- obj.denominators
127
+ obj.numerators.to_a,
128
+ obj.denominators.to_a
127
129
  )
128
130
  when :rgb_color
129
131
  Sass::Value::Color.new(
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ class Embedded
5
+ # The {Host} class.
6
+ #
7
+ # It communicates with {Dispatcher} and handles the host logic.
8
+ class Host
9
+ def initialize(channel)
10
+ @channel = channel
11
+ @mutex = Mutex.new
12
+ end
13
+
14
+ def id
15
+ @connection.id
16
+ end
17
+
18
+ def send_message(message)
19
+ @connection.send_message(message)
20
+ end
21
+
22
+ def compile_request(path:,
23
+ source:,
24
+ importer:,
25
+ load_paths:,
26
+ syntax:,
27
+ url:,
28
+ source_map:,
29
+ source_map_include_sources:,
30
+ style:,
31
+ functions:,
32
+ importers:,
33
+ alert_ascii:,
34
+ alert_color:,
35
+ logger:,
36
+ quiet_deps:,
37
+ verbose:)
38
+ async do
39
+ @function_registry = FunctionRegistry.new(functions, alert_color: alert_color)
40
+ @importer_registry = ImporterRegistry.new(importers, load_paths, alert_color: alert_color)
41
+ @logger_registry = LoggerRegistry.new(logger)
42
+
43
+ send_message EmbeddedProtocol::InboundMessage.new(
44
+ compile_request: EmbeddedProtocol::InboundMessage::CompileRequest.new(
45
+ id: id,
46
+ string: unless source.nil?
47
+ EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
48
+ source: source,
49
+ url: url&.to_s,
50
+ syntax: Protofier.to_proto_syntax(syntax),
51
+ importer: importer.nil? ? nil : @importer_registry.register(importer)
52
+ )
53
+ end,
54
+ path: path,
55
+ style: Protofier.to_proto_output_style(style),
56
+ source_map: source_map,
57
+ source_map_include_sources: source_map_include_sources,
58
+ importers: @importer_registry.importers,
59
+ global_functions: @function_registry.global_functions,
60
+ alert_ascii: alert_ascii,
61
+ alert_color: alert_color,
62
+ quiet_deps: quiet_deps,
63
+ verbose: verbose
64
+ )
65
+ )
66
+ end
67
+ end
68
+
69
+ def version_request
70
+ async do
71
+ send_message EmbeddedProtocol::InboundMessage.new(
72
+ version_request: EmbeddedProtocol::InboundMessage::VersionRequest.new(
73
+ id: id
74
+ )
75
+ )
76
+ end
77
+ end
78
+
79
+ def log_event(message)
80
+ @logger_registry.log(message)
81
+ end
82
+
83
+ def compile_response(message)
84
+ @async.resolve(message)
85
+ end
86
+
87
+ def version_response(message)
88
+ @async.resolve(message)
89
+ end
90
+
91
+ def canonicalize_request(message)
92
+ send_message EmbeddedProtocol::InboundMessage.new(
93
+ canonicalize_response: @importer_registry.canonicalize(message)
94
+ )
95
+ end
96
+
97
+ def import_request(message)
98
+ send_message EmbeddedProtocol::InboundMessage.new(
99
+ import_response: @importer_registry.import(message)
100
+ )
101
+ end
102
+
103
+ def file_import_request(message)
104
+ send_message EmbeddedProtocol::InboundMessage.new(
105
+ file_import_response: @importer_registry.file_import(message)
106
+ )
107
+ end
108
+
109
+ def function_call_request(message)
110
+ send_message EmbeddedProtocol::InboundMessage.new(
111
+ function_call_response: @function_registry.function_call(message)
112
+ )
113
+ end
114
+
115
+ def error(message)
116
+ @async.reject(CompileError.new(message.message, nil, nil, nil))
117
+ end
118
+
119
+ private
120
+
121
+ def async
122
+ @mutex.synchronize do
123
+ @connection = @channel.connect(self)
124
+ @async = Async.new
125
+ yield
126
+ @async.await
127
+ ensure
128
+ @connection.disconnect
129
+ end
130
+ end
131
+ end
132
+
133
+ private_constant :Host
134
+ end
135
+ end
@@ -40,7 +40,6 @@ module Sass
40
40
  # @example
41
41
  # Sass.render(file: 'style.css')
42
42
  # @return [Result]
43
- # @raise [ProtocolError]
44
43
  # @raise [RenderError]
45
44
  def render(**kwargs)
46
45
  instance.render(**kwargs)
@@ -71,7 +70,6 @@ module Sass
71
70
  # See {file:README.md#options} for supported options.
72
71
  #
73
72
  # @return [RenderResult]
74
- # @raise [ProtocolError]
75
73
  # @raise [RenderError]
76
74
  def render(data: nil,
77
75
  file: nil,