sass-embedded 0.1.3 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c42f58b6f4f3200d3033fd59e65e7cd33132731ab4520bafb2a375cf55577ec
4
- data.tar.gz: 2da13b64dab78a0b3fccd9a90cfcc6bd5affcd09dfe0b978aed53b69b2afa51e
3
+ metadata.gz: edeb1d7a1a3ba0d1485671e579478b4b1bc5d372350a25869cd4b0305bd2f356
4
+ data.tar.gz: 9eda21b2bdb2d7f278d03c9e5091eeec13956d9e0216ebb4a415988f8ac90b62
5
5
  SHA512:
6
- metadata.gz: d484872bd432f0092e67f699b18af1e242c982ff0e2019f9d23340593744ca58a015f5d63c76ae83d0a29e05ed362a2015f8f55bdbe472da67488e99c1cdaa8f
7
- data.tar.gz: bd452474519dabcd9d793205d8f0dcc64482a5313d48d9eda4464f0a5d6ed8b9d6a34ea1c57e4a8f178beafb008b080481fd6059ab2f3ad53494e2acd272527b
6
+ metadata.gz: 2cc608b5597a0c820633f1dc386de5e9125b00ed99004c5237e0223d5a181d4782da4b2025983f86fad47409ef738e1a5954e810f35e9f443dd319035b2aa4dc
7
+ data.tar.gz: a45ad64a3670c8d6f2136a287773eed2f77abaeb6911173931af8570fa1edeeed53a747ebc747288dc5b1941c08eb9ddc5c410ede3c536c380dd3297e0bf8092
data/lib/sass.rb CHANGED
@@ -4,8 +4,8 @@ module Sass
4
4
  # The global include_paths for Sass files. This is meant for plugins and
5
5
  # libraries to register the paths to their Sass stylesheets to that they may
6
6
  # be `@imported`. This include path is used by every instance of
7
- # {Sass::Embedded::Compiler}. They are lower-precedence than any include
8
- # paths passed in via the `:include_paths` option.
7
+ # {Sass::Embedded}. They are lower-precedence than any include paths passed
8
+ # in via the `:include_paths` option.
9
9
  #
10
10
  # If the `SASS_PATH` environment variable is set,
11
11
  # the initial value of `include_paths` will be initialized based on that.
@@ -24,13 +24,13 @@ module Sass
24
24
  end
25
25
 
26
26
  def self.render(options)
27
- unless defined? @compiler
28
- @compiler = Sass::Embedded::Compiler.new
27
+ unless defined? @embedded
28
+ @embedded = Sass::Embedded.new
29
29
  at_exit do
30
- @compiler.close
30
+ @embedded.close
31
31
  end
32
32
  end
33
- @compiler.render options
33
+ @embedded.render options
34
34
  end
35
35
  end
36
36
 
@@ -38,5 +38,5 @@ require_relative 'sass/version'
38
38
  require_relative 'sass/error'
39
39
  require_relative 'sass/platform'
40
40
  require_relative 'sass/util'
41
- require_relative 'sass/embedded/transport'
42
- require_relative 'sass/embedded/compiler'
41
+ require_relative 'sass/transport'
42
+ require_relative 'sass/embedded'
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ class Embedded
5
+ def initialize
6
+ @transport = Transport.new
7
+ @id_semaphore = Mutex.new
8
+ @id = 0
9
+ end
10
+
11
+ def render(options)
12
+ start = Sass::Util.now
13
+
14
+ raise Sass::NotRenderedError, 'Either :data or :file must be set.' if options[:file].nil? && options[:data].nil?
15
+
16
+ string = if options[:data]
17
+ Sass::EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
18
+ source: options[:data],
19
+ url: options[:file] ? Sass::Util.file_uri(options[:file]) : 'stdin',
20
+ syntax: options[:indented_syntax] == true ? Sass::EmbeddedProtocol::Syntax::INDENTED : Sass::EmbeddedProtocol::Syntax::SCSS
21
+ )
22
+ end
23
+
24
+ path = options[:data] ? nil : options[:file]
25
+
26
+ style = case options[:output_style]&.to_sym
27
+ when :expanded, nil
28
+ Sass::EmbeddedProtocol::OutputStyle::EXPANDED
29
+ when :compressed
30
+ Sass::EmbeddedProtocol::OutputStyle::COMPRESSED
31
+ when :nested, :compact
32
+ raise Sass::UnsupportedValue, "#{options[:output_style]} is not a supported :output_style"
33
+ else
34
+ raise Sass::InvalidStyleError, "#{options[:output_style]} is not a valid :output_style"
35
+ end
36
+
37
+ source_map = options[:source_map].is_a?(String) || (options[:source_map] == true && !options[:out_file].nil?)
38
+
39
+ # 1. Loading a file relative to the file in which the @use or @import appeared.
40
+ # 2. Each custom importer.
41
+ # 3. Loading a file relative to the current working directory.
42
+ # 4. Each load path in includePaths
43
+ # 5. Each load path specified in the SASS_PATH environment variable, which should be semicolon-separated on Windows and colon-separated elsewhere.
44
+ importers = (if options[:importer]
45
+ [
46
+ Sass::EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(importer_id: 0)
47
+ ]
48
+ else
49
+ []
50
+ end).concat(
51
+ (options[:include_paths] || []).concat(Sass.include_paths)
52
+ .map do |include_path|
53
+ Sass::EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
54
+ path: File.absolute_path(include_path)
55
+ )
56
+ end
57
+ )
58
+
59
+ signatures = []
60
+ functions = {}
61
+ options[:functions]&.each do |signature, function|
62
+ signatures.push signature
63
+ functions[signature.to_s.split('(')[0].chomp] = function
64
+ end
65
+
66
+ compilation_id = next_id
67
+
68
+ compile_request = Sass::EmbeddedProtocol::InboundMessage::CompileRequest.new(
69
+ id: compilation_id,
70
+ string: string,
71
+ path: path,
72
+ style: style,
73
+ source_map: source_map,
74
+ importers: importers,
75
+ global_functions: options[:functions] ? signatures : [],
76
+ alert_color: true,
77
+ alert_ascii: true
78
+ )
79
+
80
+ response = @transport.send compile_request, compilation_id
81
+
82
+ file = options[:file] || 'stdin'
83
+ canonicalizations = {}
84
+ imports = {}
85
+
86
+ loop do
87
+ case response
88
+ when Sass::EmbeddedProtocol::OutboundMessage::CompileResponse
89
+ break
90
+ when Sass::EmbeddedProtocol::OutboundMessage::CanonicalizeRequest
91
+ url = Sass::Util.file_uri(File.absolute_path(response.url, File.dirname(file)))
92
+
93
+ if canonicalizations.key? url
94
+ canonicalizations[url].id = response.id
95
+ else
96
+ resolved = nil
97
+ options[:importer].each do |importer|
98
+ begin
99
+ resolved = importer.call response.url, file
100
+ rescue StandardError => e
101
+ resolved = e
102
+ end
103
+ break if resolved
104
+ end
105
+ if resolved.nil?
106
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
107
+ id: response.id,
108
+ url: url
109
+ )
110
+ elsif resolved.is_a? StandardError
111
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
112
+ id: response.id,
113
+ error: resolved.message
114
+ )
115
+ elsif resolved.key? :contents
116
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
117
+ id: response.id,
118
+ url: url
119
+ )
120
+ imports[url] = Sass::EmbeddedProtocol::InboundMessage::ImportResponse.new(
121
+ id: response.id,
122
+ success: Sass::EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
123
+ contents: resolved[:contents],
124
+ syntax: Sass::EmbeddedProtocol::Syntax::SCSS,
125
+ source_map_url: nil
126
+ )
127
+ )
128
+ elsif resolved.key? :file
129
+ canonicalized_url = Sass::Util.file_uri(resolved[:file])
130
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
131
+ id: response.id,
132
+ url: canonicalized_url
133
+ )
134
+ imports[canonicalized_url] = Sass::EmbeddedProtocol::InboundMessage::ImportResponse.new(
135
+ id: response.id,
136
+ success: Sass::EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
137
+ contents: File.read(resolved[:file]),
138
+ syntax: Sass::EmbeddedProtocol::Syntax::SCSS,
139
+ source_map_url: nil
140
+ )
141
+ )
142
+ else
143
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
144
+ id: response.id,
145
+ error: "Unexpected value returned from importer: #{resolved}"
146
+ )
147
+ end
148
+ end
149
+
150
+ response = @transport.send canonicalizations[url], compilation_id
151
+ when Sass::EmbeddedProtocol::OutboundMessage::ImportRequest
152
+ url = response.url
153
+
154
+ if imports.key? url
155
+ imports[url].id = response.id
156
+ else
157
+ imports[url] = Sass::EmbeddedProtocol::InboundMessage::ImportResponse.new(
158
+ id: response.id,
159
+ error: "Failed to import: #{url}"
160
+ )
161
+ end
162
+
163
+ response = @transport.send imports[url], compilation_id
164
+ when Sass::EmbeddedProtocol::OutboundMessage::FunctionCallRequest
165
+ begin
166
+ message = Sass::EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
167
+ id: response.id,
168
+ success: functions[response.name].call(*response.arguments)
169
+ )
170
+ rescue StandardError => e
171
+ message = Sass::EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
172
+ id: response.id,
173
+ error: e.message
174
+ )
175
+ end
176
+
177
+ response = @transport.send message, compilation_id
178
+ when Sass::EmbeddedProtocol::ProtocolError
179
+ raise Sass::ProtocolError, response.message
180
+ else
181
+ raise Sass::ProtocolError, "Unexpected packet received: #{response}"
182
+ end
183
+ end
184
+
185
+ if response.failure
186
+ raise Sass::CompilationError.new(
187
+ response.failure.message,
188
+ response.failure.formatted,
189
+ response.failure.span ? response.failure.span.url : nil,
190
+ response.failure.span ? response.failure.span.start.line + 1 : nil,
191
+ response.failure.span ? response.failure.span.start.column + 1 : nil,
192
+ 1
193
+ )
194
+ end
195
+
196
+ finish = Sass::Util.now
197
+
198
+ {
199
+ css: response.success.css,
200
+ map: response.success.source_map,
201
+ stats: {
202
+ entry: options[:file] || 'data',
203
+ start: start,
204
+ end: finish,
205
+ duration: finish - start
206
+ }
207
+ }
208
+ end
209
+
210
+ def close
211
+ @transport.close
212
+ end
213
+
214
+ private
215
+
216
+ def info
217
+ version_response = @transport.send Sass::EmbeddedProtocol::InboundMessage::VersionRequest.new(
218
+ id: next_id
219
+ )
220
+ {
221
+ compiler_version: version_response.compiler_version,
222
+ protocol_version: version_response.protocol_version,
223
+ implementation_name: version_response.implementation_name,
224
+ implementation_version: version_response.implementation_version
225
+ }
226
+ end
227
+
228
+ def next_id
229
+ @id_semaphore.synchronize do
230
+ @id += 1
231
+ @id = 0 if @id == Transport::PROTOCOL_ERROR_ID
232
+ @id
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'observer'
5
+ require_relative '../../ext/sass_embedded/embedded_sass_pb'
6
+
7
+ module Sass
8
+ class Transport
9
+ include Observable
10
+
11
+ DART_SASS_EMBEDDED = File.absolute_path(
12
+ "../../ext/sass_embedded/sass_embedded/dart-sass-embedded#{Sass::Platform::OS == 'windows' ? '.bat' : ''}", __dir__
13
+ )
14
+
15
+ PROTOCOL_ERROR_ID = 4_294_967_295
16
+
17
+ def initialize
18
+ @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(DART_SASS_EMBEDDED)
19
+ @stdin_semaphore = Mutex.new
20
+ @observerable_semaphore = Mutex.new
21
+
22
+ Thread.new do
23
+ loop do
24
+ bits = length = 0
25
+ loop do
26
+ byte = @stdout.readbyte
27
+ length += (byte & 0x7f) << bits
28
+ bits += 7
29
+ break if byte <= 0x7f
30
+ end
31
+ changed
32
+ payload = @stdout.read length
33
+ @observerable_semaphore.synchronize do
34
+ notify_observers nil, Sass::EmbeddedProtocol::OutboundMessage.decode(payload)
35
+ end
36
+ rescue Interrupt
37
+ break
38
+ rescue IOError => e
39
+ notify_observers e, nil
40
+ close
41
+ break
42
+ end
43
+ end
44
+
45
+ Thread.new do
46
+ loop do
47
+ warn @stderr.read
48
+ rescue Interrupt
49
+ break
50
+ rescue IOError => e
51
+ @observerable_semaphore.synchronize do
52
+ notify_observers e, nil
53
+ end
54
+ close
55
+ break
56
+ end
57
+ end
58
+ end
59
+
60
+ def send(req, id)
61
+ mutex = Mutex.new
62
+ resource = ConditionVariable.new
63
+
64
+ req_name = req.class.name.split('::').last.gsub(/\B(?=[A-Z])/, '_').downcase
65
+
66
+ message = Sass::EmbeddedProtocol::InboundMessage.new(req_name.to_sym => req)
67
+
68
+ error = nil
69
+ res = nil
70
+
71
+ @observerable_semaphore.synchronize do
72
+ MessageObserver.new self, id do |e, r|
73
+ mutex.synchronize do
74
+ error = e
75
+ res = r
76
+
77
+ resource.signal
78
+ end
79
+ end
80
+ end
81
+
82
+ mutex.synchronize do
83
+ write message.to_proto
84
+
85
+ resource.wait(mutex)
86
+ end
87
+
88
+ raise error if error
89
+
90
+ res
91
+ end
92
+
93
+ def close
94
+ delete_observers
95
+ @stdin.close unless @stdin.closed?
96
+ @stdout.close unless @stdout.closed?
97
+ @stderr.close unless @stderr.closed?
98
+ end
99
+
100
+ private
101
+
102
+ def write(proto)
103
+ @stdin_semaphore.synchronize do
104
+ length = proto.length
105
+ while length.positive?
106
+ @stdin.write ((length > 0x7f ? 0x80 : 0) | (length & 0x7f)).chr
107
+ length >>= 7
108
+ end
109
+ @stdin.write proto
110
+ end
111
+ end
112
+ end
113
+
114
+ class MessageObserver
115
+ def initialize(obs, id, &block)
116
+ @obs = obs
117
+ @id = id
118
+ @block = block
119
+ @obs.add_observer self
120
+ end
121
+
122
+ def update(error, message)
123
+ if error
124
+ @obs.delete_observer self
125
+ @block.call error, nil
126
+ elsif message.error&.id == Sass::Transport::PROTOCOL_ERROR_ID
127
+ @obs.delete_observer self
128
+ @block.call Sass::ProtocolError.new(message.error.message), nil
129
+ else
130
+ res = message[message.message.to_s]
131
+ if (res['compilation_id'] || res['id']) == @id
132
+ @obs.delete_observer self
133
+ @block.call error, res
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
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.1.3'
4
+ VERSION = '0.2.1'
5
5
  end
@@ -3,19 +3,19 @@
3
3
  require_relative 'test_helper'
4
4
 
5
5
  module Sass
6
- class CompilerTest < MiniTest::Test
6
+ class EmbeddedTest < MiniTest::Test
7
7
  include TempFileTest
8
8
 
9
9
  def setup
10
- @compiler = Embedded::Compiler.new
10
+ @embedded = Embedded.new
11
11
  end
12
12
 
13
13
  def teardown
14
- @compiler.close
14
+ @embedded.close
15
15
  end
16
16
 
17
17
  def render(data)
18
- @compiler.render({ data: data })[:css]
18
+ @embedded.render({ data: data })[:css]
19
19
  end
20
20
 
21
21
  def test_line_comments
@@ -31,7 +31,7 @@ module Sass
31
31
  baz: bang;
32
32
  }
33
33
  CSS
34
- output = @compiler.render({
34
+ output = @embedded.render({
35
35
  data: template,
36
36
  source_comments: true
37
37
  })
@@ -97,7 +97,7 @@ module Sass
97
97
  baz: 0.33333333;
98
98
  }
99
99
  CSS
100
- output = @compiler.render({
100
+ output = @embedded.render({
101
101
  data: template,
102
102
  precision: 8
103
103
  })
@@ -134,7 +134,7 @@ module Sass
134
134
  padding: 20px;
135
135
  }
136
136
  SCSS
137
- output = @compiler.render({
137
+ output = @embedded.render({
138
138
  data: File.read('style.scss'),
139
139
  source_map: 'style.scss.map'
140
140
  })
@@ -143,7 +143,7 @@ module Sass
143
143
  end
144
144
 
145
145
  def test_no_source_map
146
- output = @compiler.render({
146
+ output = @embedded.render({
147
147
  data: '$size: 30px;'
148
148
  })
149
149
  assert_equal '', output[:map]
@@ -157,7 +157,7 @@ module Sass
157
157
  temp_file('included_2/import.scss', "@use 'import_parent' as *; $size: $s;")
158
158
  temp_file('styles.scss', "@use 'import.scss' as *; .hi { width: $size; }")
159
159
 
160
- assert_equal ".hi {\n width: 30px;\n}", @compiler.render({
160
+ assert_equal ".hi {\n width: 30px;\n}", @embedded.render({
161
161
  data: File.read('styles.scss'),
162
162
  include_paths: %w[
163
163
  included_1 included_2
@@ -216,9 +216,9 @@ module Sass
216
216
  }
217
217
  CSS
218
218
 
219
- assert_equal css, @compiler.render({ data: sass, indented_syntax: true })[:css]
219
+ assert_equal css, @embedded.render({ data: sass, indented_syntax: true })[:css]
220
220
  assert_raises(CompilationError) do
221
- @compiler.render({ data: sass, indented_syntax: false })
221
+ @embedded.render({ data: sass, indented_syntax: false })
222
222
  end
223
223
  end
224
224
 
@@ -230,7 +230,7 @@ module Sass
230
230
  baz: bang; }
231
231
  SCSS
232
232
 
233
- output = @compiler.render({
233
+ output = @embedded.render({
234
234
  data: template,
235
235
  source_map: '.',
236
236
  source_map_embed: true,
@@ -263,7 +263,7 @@ module Sass
263
263
  threads = []
264
264
  10.times do |i|
265
265
  threads << Thread.new(i) do |id|
266
- output = @compiler.render({
266
+ output = @embedded.render({
267
267
  data: "div { width: #{id} }"
268
268
  })[:css]
269
269
  assert_match(/#{id}/, output)
@@ -7,15 +7,15 @@ module Sass
7
7
  include TempFileTest
8
8
 
9
9
  def setup
10
- @compiler = Embedded::Compiler.new
10
+ @embedded = Embedded.new
11
11
  end
12
12
 
13
13
  def teardown
14
- @compiler.close
14
+ @embedded.close
15
15
  end
16
16
 
17
17
  def render(data, importer)
18
- @compiler.render({ data: data, importer: importer })[:css]
18
+ @embedded.render({ data: data, importer: importer })[:css]
19
19
  end
20
20
 
21
21
  def test_custom_importer_works
@@ -116,7 +116,7 @@ module Sass
116
116
  end
117
117
 
118
118
  def test_parent_path_is_accessible
119
- output = @compiler.render({
119
+ output = @embedded.render({
120
120
  data: "@import 'parent.scss';",
121
121
  file: 'import-parent-filename.scss',
122
122
  importer: [
@@ -133,13 +133,13 @@ module Sass
133
133
  CSS
134
134
  end
135
135
 
136
- def test_call_compiler_importer
137
- output = @compiler.render({
136
+ def test_call_embedded_importer
137
+ output = @embedded.render({
138
138
  data: "@import 'parent.scss';",
139
139
  importer: [
140
140
  lambda { |_url, _prev|
141
141
  {
142
- contents: @compiler.render({
142
+ contents: @embedded.render({
143
143
  data: "@import 'parent-parent.scss'",
144
144
  importer: [
145
145
  lambda { |_url, _prev|
data/test/error_test.rb CHANGED
@@ -5,11 +5,11 @@ require_relative 'test_helper'
5
5
  module Sass
6
6
  class ErrorTest < MiniTest::Test
7
7
  def setup
8
- @compiler = Embedded::Compiler.new
8
+ @embedded = Embedded.new
9
9
  end
10
10
 
11
11
  def teardown
12
- @compiler.close
12
+ @embedded.close
13
13
  end
14
14
 
15
15
  def test_first_backtrace_is_sass
@@ -20,7 +20,7 @@ module Sass
20
20
  }
21
21
  SCSS
22
22
 
23
- @compiler.render({
23
+ @embedded.render({
24
24
  data: template
25
25
  })
26
26
  rescue Sass::CompilationError => e
@@ -5,15 +5,15 @@ require_relative 'test_helper'
5
5
  module Sass
6
6
  class FunctionsTest < MiniTest::Test
7
7
  def setup
8
- @compiler = Embedded::Compiler.new
8
+ @embedded = Embedded.new
9
9
  end
10
10
 
11
11
  def teardown
12
- @compiler.close
12
+ @embedded.close
13
13
  end
14
14
 
15
15
  def render(sass)
16
- @compiler.render({
16
+ @embedded.render({
17
17
  data: sass,
18
18
  functions: {
19
19
  'javascript_path($path)': lambda { |path|
@@ -322,7 +322,7 @@ module Sass
322
322
  threads = []
323
323
  10.times do |i|
324
324
  threads << Thread.new(i) do |id|
325
- output = @compiler.render({
325
+ output = @embedded.render({
326
326
  data: 'div { url: test-function() }',
327
327
  functions: {
328
328
  'test_function()': lambda {
@@ -346,7 +346,7 @@ module Sass
346
346
  end
347
347
 
348
348
  def test_pass_custom_functions_as_a_parameter
349
- output = @compiler.render({
349
+ output = @embedded.render({
350
350
  data: 'div { url: test-function(); }',
351
351
  functions: {
352
352
  'test_function()': lambda {
@@ -365,7 +365,7 @@ module Sass
365
365
 
366
366
  def test_pass_incompatible_type_to_custom_functions
367
367
  assert_raises(CompilationError) do
368
- @compiler.render({
368
+ @embedded.render({
369
369
  data: 'div { url: test-function(); }',
370
370
  functions: {
371
371
  'test_function()': lambda {
@@ -5,11 +5,11 @@ require_relative 'test_helper'
5
5
  module Sass
6
6
  class OutputStyleTest < MiniTest::Test
7
7
  def setup
8
- @compiler = Embedded::Compiler.new
8
+ @embedded = Embedded.new
9
9
  end
10
10
 
11
11
  def teardown
12
- @compiler.close
12
+ @embedded.close
13
13
  end
14
14
 
15
15
  def input_scss
@@ -51,40 +51,40 @@ module Sass
51
51
  end
52
52
 
53
53
  def test_expanded_output_is_default
54
- output = @compiler.render({ data: input_scss })[:css]
54
+ output = @embedded.render({ data: input_scss })[:css]
55
55
  assert_equal expected_expanded_output, output
56
56
  end
57
57
 
58
58
  def test_output_style_accepts_strings
59
- output = @compiler.render({ data: input_scss, output_style: :expanded })[:css]
59
+ output = @embedded.render({ data: input_scss, output_style: :expanded })[:css]
60
60
  assert_equal expected_expanded_output, output
61
61
  end
62
62
 
63
63
  def test_invalid_output_style
64
64
  assert_raises(InvalidStyleError) do
65
- @compiler.render({ data: input_scss, output_style: :totally_wrong })[:css]
65
+ @embedded.render({ data: input_scss, output_style: :totally_wrong })[:css]
66
66
  end
67
67
  end
68
68
 
69
69
  def test_unsupported_output_style
70
70
  assert_raises(UnsupportedValue) do
71
- @compiler.render({ data: input_scss, output_style: :nested })[:css]
71
+ @embedded.render({ data: input_scss, output_style: :nested })[:css]
72
72
  end
73
73
 
74
74
  assert_raises(UnsupportedValue) do
75
- @compiler.render({ data: input_scss, output_style: :compact })[:css]
75
+ @embedded.render({ data: input_scss, output_style: :compact })[:css]
76
76
  end
77
77
  end
78
78
 
79
79
  def test_compressed_output
80
- output = @compiler.render({ data: input_scss, output_style: :compressed })[:css]
80
+ output = @embedded.render({ data: input_scss, output_style: :compressed })[:css]
81
81
  assert_equal <<~CSS.chomp, output
82
82
  #main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline}
83
83
  CSS
84
84
  end
85
85
 
86
86
  def test_string_output_style_names
87
- output = @compiler.render({ data: input_scss, output_style: 'compressed' })[:css]
87
+ output = @embedded.render({ data: input_scss, output_style: 'compressed' })[:css]
88
88
  assert_equal <<~CSS.chomp, output
89
89
  #main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline}
90
90
  CSS
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.1.3
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - なつき
@@ -126,10 +126,10 @@ files:
126
126
  - ext/sass_embedded/Makefile
127
127
  - ext/sass_embedded/extconf.rb
128
128
  - lib/sass.rb
129
- - lib/sass/embedded/compiler.rb
130
- - lib/sass/embedded/transport.rb
129
+ - lib/sass/embedded.rb
131
130
  - lib/sass/error.rb
132
131
  - lib/sass/platform.rb
132
+ - lib/sass/transport.rb
133
133
  - lib/sass/util.rb
134
134
  - lib/sass/version.rb
135
135
  - sass-embedded.gemspec
@@ -1,238 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sass
4
- module Embedded
5
- class Compiler
6
- def initialize
7
- @transport = Transport.new
8
- @id_semaphore = Mutex.new
9
- @id = 0
10
- end
11
-
12
- def render(options)
13
- start = Sass::Util.now
14
-
15
- raise Sass::NotRenderedError, 'Either :data or :file must be set.' if options[:file].nil? && options[:data].nil?
16
-
17
- string = if options[:data]
18
- Sass::EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
19
- source: options[:data],
20
- url: options[:file] ? Sass::Util.file_uri(options[:file]) : 'stdin',
21
- syntax: options[:indented_syntax] == true ? Sass::EmbeddedProtocol::Syntax::INDENTED : Sass::EmbeddedProtocol::Syntax::SCSS
22
- )
23
- end
24
-
25
- path = options[:data] ? nil : options[:file]
26
-
27
- style = case options[:output_style]&.to_sym
28
- when :expanded, nil
29
- Sass::EmbeddedProtocol::OutputStyle::EXPANDED
30
- when :compressed
31
- Sass::EmbeddedProtocol::OutputStyle::COMPRESSED
32
- when :nested, :compact
33
- raise Sass::UnsupportedValue, "#{options[:output_style]} is not a supported :output_style"
34
- else
35
- raise Sass::InvalidStyleError, "#{options[:output_style]} is not a valid :output_style"
36
- end
37
-
38
- source_map = options[:source_map].is_a?(String) || (options[:source_map] == true && !options[:out_file].nil?)
39
-
40
- # 1. Loading a file relative to the file in which the @use or @import appeared.
41
- # 2. Each custom importer.
42
- # 3. Loading a file relative to the current working directory.
43
- # 4. Each load path in includePaths
44
- # 5. Each load path specified in the SASS_PATH environment variable, which should be semicolon-separated on Windows and colon-separated elsewhere.
45
- importers = (if options[:importer]
46
- [
47
- Sass::EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(importer_id: 0)
48
- ]
49
- else
50
- []
51
- end).concat(
52
- (options[:include_paths] || []).concat(Sass.include_paths)
53
- .map do |include_path|
54
- Sass::EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
55
- path: File.absolute_path(include_path)
56
- )
57
- end
58
- )
59
-
60
- signatures = []
61
- functions = {}
62
- options[:functions]&.each do |signature, function|
63
- signatures.push signature
64
- functions[signature.to_s.split('(')[0].chomp] = function
65
- end
66
-
67
- compilation_id = next_id
68
-
69
- compile_request = Sass::EmbeddedProtocol::InboundMessage::CompileRequest.new(
70
- id: compilation_id,
71
- string: string,
72
- path: path,
73
- style: style,
74
- source_map: source_map,
75
- importers: importers,
76
- global_functions: options[:functions] ? signatures : [],
77
- alert_color: true,
78
- alert_ascii: true
79
- )
80
-
81
- response = @transport.send compile_request, compilation_id
82
-
83
- file = options[:file] || 'stdin'
84
- canonicalizations = {}
85
- imports = {}
86
-
87
- loop do
88
- case response
89
- when Sass::EmbeddedProtocol::OutboundMessage::CompileResponse
90
- break
91
- when Sass::EmbeddedProtocol::OutboundMessage::CanonicalizeRequest
92
- url = Sass::Util.file_uri(File.absolute_path(response.url, File.dirname(file)))
93
-
94
- if canonicalizations.key? url
95
- canonicalizations[url].id = response.id
96
- else
97
- resolved = nil
98
- options[:importer].each do |importer|
99
- begin
100
- resolved = importer.call response.url, file
101
- rescue StandardError => e
102
- resolved = e
103
- end
104
- break if resolved
105
- end
106
- if resolved.nil?
107
- canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
108
- id: response.id,
109
- url: url
110
- )
111
- elsif resolved.is_a? StandardError
112
- canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
113
- id: response.id,
114
- error: resolved.message
115
- )
116
- elsif resolved.key? :contents
117
- canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
118
- id: response.id,
119
- url: url
120
- )
121
- imports[url] = Sass::EmbeddedProtocol::InboundMessage::ImportResponse.new(
122
- id: response.id,
123
- success: Sass::EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
124
- contents: resolved[:contents],
125
- syntax: Sass::EmbeddedProtocol::Syntax::SCSS,
126
- source_map_url: nil
127
- )
128
- )
129
- elsif resolved.key? :file
130
- canonicalized_url = Sass::Util.file_uri(resolved[:file])
131
- canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
132
- id: response.id,
133
- url: canonicalized_url
134
- )
135
- imports[canonicalized_url] = Sass::EmbeddedProtocol::InboundMessage::ImportResponse.new(
136
- id: response.id,
137
- success: Sass::EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
138
- contents: File.read(resolved[:file]),
139
- syntax: Sass::EmbeddedProtocol::Syntax::SCSS,
140
- source_map_url: nil
141
- )
142
- )
143
- else
144
- canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
145
- id: response.id,
146
- error: "Unexpected value returned from importer: #{resolved}"
147
- )
148
- end
149
- end
150
-
151
- response = @transport.send canonicalizations[url], compilation_id
152
- when Sass::EmbeddedProtocol::OutboundMessage::ImportRequest
153
- url = response.url
154
-
155
- if imports.key? url
156
- imports[url].id = response.id
157
- else
158
- imports[url] = Sass::EmbeddedProtocol::InboundMessage::ImportResponse.new(
159
- id: response.id,
160
- error: "Failed to import: #{url}"
161
- )
162
- end
163
-
164
- response = @transport.send imports[url], compilation_id
165
- when Sass::EmbeddedProtocol::OutboundMessage::FunctionCallRequest
166
- begin
167
- message = Sass::EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
168
- id: response.id,
169
- success: functions[response.name].call(*response.arguments)
170
- )
171
- rescue StandardError => e
172
- message = Sass::EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
173
- id: response.id,
174
- error: e.message
175
- )
176
- end
177
-
178
- response = @transport.send message, compilation_id
179
- when Sass::EmbeddedProtocol::ProtocolError
180
- raise Sass::ProtocolError, response.message
181
- else
182
- raise Sass::ProtocolError, "Unexpected packet received: #{response}"
183
- end
184
- end
185
-
186
- if response.failure
187
- raise Sass::CompilationError.new(
188
- response.failure.message,
189
- response.failure.formatted,
190
- response.failure.span ? response.failure.span.url : nil,
191
- response.failure.span ? response.failure.span.start.line + 1 : nil,
192
- response.failure.span ? response.failure.span.start.column + 1 : nil,
193
- 1
194
- )
195
- end
196
-
197
- finish = Sass::Util.now
198
-
199
- {
200
- css: response.success.css,
201
- map: response.success.source_map,
202
- stats: {
203
- entry: options[:file] || 'data',
204
- start: start,
205
- end: finish,
206
- duration: finish - start
207
- }
208
- }
209
- end
210
-
211
- def close
212
- @transport.close
213
- end
214
-
215
- private
216
-
217
- def info
218
- version_response = @transport.send Sass::EmbeddedProtocol::InboundMessage::VersionRequest.new(
219
- id: next_id
220
- )
221
- {
222
- compiler_version: version_response.compiler_version,
223
- protocol_version: version_response.protocol_version,
224
- implementation_name: version_response.implementation_name,
225
- implementation_version: version_response.implementation_version
226
- }
227
- end
228
-
229
- def next_id
230
- @id_semaphore.synchronize do
231
- @id += 1
232
- @id = 0 if @id == Transport::PROTOCOL_ERROR_ID
233
- @id
234
- end
235
- end
236
- end
237
- end
238
- end
@@ -1,140 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open3'
4
- require 'observer'
5
- require_relative '../../../ext/sass_embedded/embedded_sass_pb'
6
-
7
- module Sass
8
- module Embedded
9
- class Transport
10
- include Observable
11
-
12
- DART_SASS_EMBEDDED = File.absolute_path(
13
- "../../../ext/sass_embedded/sass_embedded/dart-sass-embedded#{Sass::Platform::OS == 'windows' ? '.bat' : ''}", __dir__
14
- )
15
-
16
- PROTOCOL_ERROR_ID = 4_294_967_295
17
-
18
- def initialize
19
- @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(DART_SASS_EMBEDDED)
20
- @stdin_semaphore = Mutex.new
21
- @observerable_semaphore = Mutex.new
22
-
23
- Thread.new do
24
- loop do
25
- bits = length = 0
26
- loop do
27
- byte = @stdout.readbyte
28
- length += (byte & 0x7f) << bits
29
- bits += 7
30
- break if byte <= 0x7f
31
- end
32
- changed
33
- payload = @stdout.read length
34
- @observerable_semaphore.synchronize do
35
- notify_observers nil, Sass::EmbeddedProtocol::OutboundMessage.decode(payload)
36
- end
37
- rescue Interrupt
38
- break
39
- rescue IOError => e
40
- notify_observers e, nil
41
- close
42
- break
43
- end
44
- end
45
-
46
- Thread.new do
47
- loop do
48
- warn @stderr.read
49
- rescue Interrupt
50
- break
51
- rescue IOError => e
52
- @observerable_semaphore.synchronize do
53
- notify_observers e, nil
54
- end
55
- close
56
- break
57
- end
58
- end
59
- end
60
-
61
- def send(req, id)
62
- mutex = Mutex.new
63
- resource = ConditionVariable.new
64
-
65
- req_name = req.class.name.split('::').last.gsub(/\B(?=[A-Z])/, '_').downcase
66
-
67
- message = Sass::EmbeddedProtocol::InboundMessage.new(req_name.to_sym => req)
68
-
69
- error = nil
70
- res = nil
71
-
72
- @observerable_semaphore.synchronize do
73
- MessageObserver.new self, id do |e, r|
74
- mutex.synchronize do
75
- error = e
76
- res = r
77
-
78
- resource.signal
79
- end
80
- end
81
- end
82
-
83
- mutex.synchronize do
84
- write message.to_proto
85
-
86
- resource.wait(mutex)
87
- end
88
-
89
- raise error if error
90
-
91
- res
92
- end
93
-
94
- def close
95
- delete_observers
96
- @stdin.close unless @stdin.closed?
97
- @stdout.close unless @stdout.closed?
98
- @stderr.close unless @stderr.closed?
99
- end
100
-
101
- private
102
-
103
- def write(proto)
104
- @stdin_semaphore.synchronize do
105
- length = proto.length
106
- while length.positive?
107
- @stdin.write ((length > 0x7f ? 0x80 : 0) | (length & 0x7f)).chr
108
- length >>= 7
109
- end
110
- @stdin.write proto
111
- end
112
- end
113
- end
114
-
115
- class MessageObserver
116
- def initialize(obs, id, &block)
117
- @obs = obs
118
- @id = id
119
- @block = block
120
- @obs.add_observer self
121
- end
122
-
123
- def update(error, message)
124
- if error
125
- @obs.delete_observer self
126
- @block.call error, nil
127
- elsif message.error&.id == Sass::Embedded::Transport::PROTOCOL_ERROR_ID
128
- @obs.delete_observer self
129
- @block.call Sass::ProtocolError.new(message.error.message), nil
130
- else
131
- res = message[message.message.to_s]
132
- if (res['compilation_id'] ? res['compilation_id'] : res['id']) == @id
133
- @obs.delete_observer self
134
- @block.call error, res
135
- end
136
- end
137
- end
138
- end
139
- end
140
- end