sass-embedded 0.2.4 → 0.3.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/.github/workflows/build.yml +1 -1
- data/.rubocop.yml +14 -0
- data/README.md +1 -3
- data/Rakefile +9 -4
- data/ext/extconf.rb +2 -1
- data/lib/sass.rb +10 -2
- data/lib/sass/embedded.rb +243 -163
- data/lib/sass/error.rb +4 -9
- data/lib/sass/transport.rb +59 -48
- data/lib/sass/util.rb +1 -0
- data/lib/sass/version.rb +1 -1
- data/sass-embedded.gemspec +3 -1
- data/test/compiler_test.rb +22 -36
- data/test/custom_importer_test.rb +23 -30
- data/test/error_test.rb +2 -4
- data/test/functions_test.rb +149 -157
- data/test/output_style_test.rb +10 -10
- data/test/sass_test.rb +2 -6
- metadata +32 -3
data/lib/sass/error.rb
CHANGED
@@ -1,17 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Sass
|
4
|
-
class
|
4
|
+
class SassError < StandardError; end
|
5
5
|
|
6
|
-
class ProtocolError <
|
6
|
+
class ProtocolError < SassError; end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
class InvalidStyleError < BaseError; end
|
11
|
-
|
12
|
-
class UnsupportedValue < BaseError; end
|
13
|
-
|
14
|
-
class CompilationError < BaseError
|
8
|
+
# The error returned by {Sass.render}
|
9
|
+
class RenderError < SassError
|
15
10
|
attr_accessor :formatted, :file, :line, :column, :status
|
16
11
|
|
17
12
|
def initialize(message, formatted, file, line, column, status)
|
data/lib/sass/transport.rb
CHANGED
@@ -5,71 +5,39 @@ 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
11
|
class Transport
|
9
12
|
include Observable
|
10
13
|
|
11
14
|
DART_SASS_EMBEDDED = File.absolute_path(
|
12
|
-
"../../ext/sass_embedded/dart-sass-embedded#{
|
15
|
+
"../../ext/sass_embedded/dart-sass-embedded#{Platform::OS == 'windows' ? '.bat' : ''}", __dir__
|
13
16
|
)
|
14
17
|
|
15
18
|
PROTOCOL_ERROR_ID = 4_294_967_295
|
16
19
|
|
17
20
|
def initialize
|
18
|
-
@stdin, @stdout, @stderr, @wait_thread = Open3.popen3(DART_SASS_EMBEDDED)
|
19
21
|
@stdin_semaphore = Mutex.new
|
20
22
|
@observerable_semaphore = Mutex.new
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
23
|
+
@stdin, @stdout, @stderr, @wait_thread = Open3.popen3(DART_SASS_EMBEDDED)
|
24
|
+
pipe @stderr, $stderr
|
25
|
+
receive
|
58
26
|
end
|
59
27
|
|
60
|
-
def send(req,
|
28
|
+
def send(req, res_id)
|
61
29
|
mutex = Mutex.new
|
62
30
|
resource = ConditionVariable.new
|
63
31
|
|
64
|
-
|
32
|
+
req_kind = req.class.name.split('::').last.gsub(/\B(?=[A-Z])/, '_').downcase
|
65
33
|
|
66
|
-
message =
|
34
|
+
message = EmbeddedProtocol::InboundMessage.new(req_kind => req)
|
67
35
|
|
68
36
|
error = nil
|
69
37
|
res = nil
|
70
38
|
|
71
39
|
@observerable_semaphore.synchronize do
|
72
|
-
MessageObserver.new self,
|
40
|
+
MessageObserver.new self, res_id do |e, r|
|
73
41
|
mutex.synchronize do
|
74
42
|
error = e
|
75
43
|
res = r
|
@@ -100,17 +68,60 @@ module Sass
|
|
100
68
|
|
101
69
|
private
|
102
70
|
|
103
|
-
def
|
71
|
+
def receive
|
72
|
+
Thread.new do
|
73
|
+
loop do
|
74
|
+
bits = length = 0
|
75
|
+
loop do
|
76
|
+
byte = @stdout.readbyte
|
77
|
+
length += (byte & 0x7f) << bits
|
78
|
+
bits += 7
|
79
|
+
break if byte <= 0x7f
|
80
|
+
end
|
81
|
+
changed
|
82
|
+
payload = @stdout.read length
|
83
|
+
@observerable_semaphore.synchronize do
|
84
|
+
notify_observers nil, EmbeddedProtocol::OutboundMessage.decode(payload)
|
85
|
+
end
|
86
|
+
rescue Interrupt
|
87
|
+
break
|
88
|
+
rescue IOError => e
|
89
|
+
notify_observers e, nil
|
90
|
+
close
|
91
|
+
break
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def pipe(readable, writeable)
|
97
|
+
Thread.new do
|
98
|
+
loop do
|
99
|
+
writeable.write readable.read
|
100
|
+
rescue Interrupt
|
101
|
+
break
|
102
|
+
rescue IOError => e
|
103
|
+
@observerable_semaphore.synchronize do
|
104
|
+
notify_observers e, nil
|
105
|
+
end
|
106
|
+
close
|
107
|
+
break
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def write(payload)
|
104
113
|
@stdin_semaphore.synchronize do
|
105
|
-
length =
|
114
|
+
length = payload.length
|
106
115
|
while length.positive?
|
107
116
|
@stdin.write ((length > 0x7f ? 0x80 : 0) | (length & 0x7f)).chr
|
108
117
|
length >>= 7
|
109
118
|
end
|
110
|
-
@stdin.write
|
119
|
+
@stdin.write payload
|
111
120
|
end
|
112
121
|
end
|
113
122
|
|
123
|
+
# The observer used to listen on messages from stdout, check if id
|
124
|
+
# matches the given request id, and yield back to the given block.
|
114
125
|
class MessageObserver
|
115
126
|
def initialize(obs, id, &block)
|
116
127
|
@obs = obs
|
@@ -123,9 +134,9 @@ module Sass
|
|
123
134
|
if error
|
124
135
|
@obs.delete_observer self
|
125
136
|
@block.call error, nil
|
126
|
-
elsif message.error&.id ==
|
137
|
+
elsif message.error&.id == Transport::PROTOCOL_ERROR_ID
|
127
138
|
@obs.delete_observer self
|
128
|
-
@block.call
|
139
|
+
@block.call ProtocolError.new(message.error.message), nil
|
129
140
|
else
|
130
141
|
res = message[message.message.to_s]
|
131
142
|
if (res['compilation_id'] || res['id']) == @id
|
data/lib/sass/util.rb
CHANGED
data/lib/sass/version.rb
CHANGED
data/sass-embedded.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
21
|
|
22
|
-
spec.required_ruby_version = '>= 2.
|
22
|
+
spec.required_ruby_version = '>= 2.6'
|
23
23
|
|
24
24
|
spec.require_paths = ['lib']
|
25
25
|
|
@@ -33,4 +33,6 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_development_dependency 'rake'
|
34
34
|
spec.add_development_dependency 'rake-compiler'
|
35
35
|
spec.add_development_dependency 'rubocop'
|
36
|
+
spec.add_development_dependency 'rubocop-minitest'
|
37
|
+
spec.add_development_dependency 'rubocop-rake'
|
36
38
|
end
|
data/test/compiler_test.rb
CHANGED
@@ -15,7 +15,7 @@ module Sass
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def render(data)
|
18
|
-
@embedded.render(
|
18
|
+
@embedded.render(data: data)[:css]
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_line_comments
|
@@ -31,10 +31,8 @@ module Sass
|
|
31
31
|
baz: bang;
|
32
32
|
}
|
33
33
|
CSS
|
34
|
-
output = @embedded.render(
|
35
|
-
|
36
|
-
source_comments: true
|
37
|
-
})
|
34
|
+
output = @embedded.render(data: template,
|
35
|
+
source_comments: true)
|
38
36
|
assert_equal expected_output, output[:css]
|
39
37
|
end
|
40
38
|
|
@@ -97,10 +95,8 @@ module Sass
|
|
97
95
|
baz: 0.33333333;
|
98
96
|
}
|
99
97
|
CSS
|
100
|
-
output = @embedded.render(
|
101
|
-
|
102
|
-
precision: 8
|
103
|
-
})
|
98
|
+
output = @embedded.render(data: template,
|
99
|
+
precision: 8)
|
104
100
|
assert_equal expected_output, output
|
105
101
|
end
|
106
102
|
|
@@ -134,18 +130,14 @@ module Sass
|
|
134
130
|
padding: 20px;
|
135
131
|
}
|
136
132
|
SCSS
|
137
|
-
output = @embedded.render(
|
138
|
-
|
139
|
-
source_map: 'style.scss.map'
|
140
|
-
})
|
133
|
+
output = @embedded.render(data: File.read('style.scss'),
|
134
|
+
source_map: 'style.scss.map')
|
141
135
|
|
142
136
|
assert output[:map].start_with? '{"version":3,'
|
143
137
|
end
|
144
138
|
|
145
139
|
def test_no_source_map
|
146
|
-
output = @embedded.render(
|
147
|
-
data: '$size: 30px;'
|
148
|
-
})
|
140
|
+
output = @embedded.render(data: '$size: 30px;')
|
149
141
|
assert_equal '', output[:map]
|
150
142
|
end
|
151
143
|
|
@@ -157,12 +149,10 @@ module Sass
|
|
157
149
|
temp_file('included_2/import.scss', "@use 'import_parent' as *; $size: $s;")
|
158
150
|
temp_file('styles.scss', "@use 'import.scss' as *; .hi { width: $size; }")
|
159
151
|
|
160
|
-
assert_equal ".hi {\n width: 30px;\n}", @embedded.render(
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
]
|
165
|
-
})[:css]
|
152
|
+
assert_equal ".hi {\n width: 30px;\n}", @embedded.render(data: File.read('styles.scss'),
|
153
|
+
include_paths: %w[
|
154
|
+
included_1 included_2
|
155
|
+
])[:css]
|
166
156
|
end
|
167
157
|
|
168
158
|
def test_global_include_paths
|
@@ -186,7 +176,7 @@ module Sass
|
|
186
176
|
|
187
177
|
ENV['SASS_PATH'] = expected_include_paths.join(File::PATH_SEPARATOR)
|
188
178
|
|
189
|
-
assert_equal expected_include_paths, Sass.include_paths
|
179
|
+
assert_equal expected_include_paths, ::Sass.include_paths
|
190
180
|
|
191
181
|
::Sass.include_paths.clear
|
192
182
|
end
|
@@ -198,7 +188,7 @@ module Sass
|
|
198
188
|
temp_file('included_6/import.scss', "@use 'import_parent' as *; $size: $s;")
|
199
189
|
temp_file('styles.scss', "@use 'import.scss' as *; .hi { width: $size; }")
|
200
190
|
|
201
|
-
assert_raises(
|
191
|
+
assert_raises(RenderError) do
|
202
192
|
render(File.read('styles.scss'))
|
203
193
|
end
|
204
194
|
end
|
@@ -216,9 +206,9 @@ module Sass
|
|
216
206
|
}
|
217
207
|
CSS
|
218
208
|
|
219
|
-
assert_equal css, @embedded.render(
|
220
|
-
assert_raises(
|
221
|
-
@embedded.render(
|
209
|
+
assert_equal css, @embedded.render(data: sass, indented_syntax: true)[:css]
|
210
|
+
assert_raises(RenderError) do
|
211
|
+
@embedded.render(data: sass, indented_syntax: false)
|
222
212
|
end
|
223
213
|
end
|
224
214
|
|
@@ -230,12 +220,10 @@ module Sass
|
|
230
220
|
baz: bang; }
|
231
221
|
SCSS
|
232
222
|
|
233
|
-
output = @embedded.render(
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
source_map_contents: true
|
238
|
-
})[:css]
|
223
|
+
output = @embedded.render(data: template,
|
224
|
+
source_map: '.',
|
225
|
+
source_map_embed: true,
|
226
|
+
source_map_contents: true)[:css]
|
239
227
|
|
240
228
|
assert_match(/sourceMappingURL/, output)
|
241
229
|
assert_match(/.foo/, output)
|
@@ -263,9 +251,7 @@ module Sass
|
|
263
251
|
threads = []
|
264
252
|
10.times do |i|
|
265
253
|
threads << Thread.new(i) do |id|
|
266
|
-
output = @embedded.render({
|
267
|
-
data: "div { width: #{id} }"
|
268
|
-
})[:css]
|
254
|
+
output = @embedded.render(data: "div { width: #{id} }")[:css]
|
269
255
|
assert_match(/#{id}/, output)
|
270
256
|
end
|
271
257
|
end
|
@@ -15,7 +15,7 @@ module Sass
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def render(data, importer)
|
18
|
-
@embedded.render(
|
18
|
+
@embedded.render(data: data, importer: importer)[:css]
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_custom_importer_works
|
@@ -86,17 +86,16 @@ module Sass
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def test_custom_importer_that_does_not_resolve
|
89
|
-
assert_raises(
|
89
|
+
assert_raises(RenderError) do
|
90
90
|
render("@import 'test.scss';", [
|
91
91
|
lambda { |_url, _prev|
|
92
|
-
return nil
|
93
92
|
}
|
94
93
|
])
|
95
94
|
end
|
96
95
|
end
|
97
96
|
|
98
97
|
def test_custom_importer_that_returns_error
|
99
|
-
assert_raises(
|
98
|
+
assert_raises(RenderError) do
|
100
99
|
render("@import 'test.scss';", [
|
101
100
|
lambda { |_url, _prev|
|
102
101
|
IOError.new 'test error'
|
@@ -106,7 +105,7 @@ module Sass
|
|
106
105
|
end
|
107
106
|
|
108
107
|
def test_custom_importer_that_raises_error
|
109
|
-
assert_raises(
|
108
|
+
assert_raises(RenderError) do
|
110
109
|
render("@import 'test.scss';", [
|
111
110
|
lambda { |_url, _prev|
|
112
111
|
raise IOError, 'test error'
|
@@ -116,15 +115,13 @@ module Sass
|
|
116
115
|
end
|
117
116
|
|
118
117
|
def test_parent_path_is_accessible
|
119
|
-
output = @embedded.render(
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
]
|
127
|
-
})[:css]
|
118
|
+
output = @embedded.render(data: "@import 'parent.scss';",
|
119
|
+
file: 'import-parent-filename.scss',
|
120
|
+
importer: [
|
121
|
+
lambda { |_url, prev|
|
122
|
+
{ contents: ".#{prev} { color: red; }" }
|
123
|
+
}
|
124
|
+
])[:css]
|
128
125
|
|
129
126
|
assert_equal <<~CSS.chomp, output
|
130
127
|
.import-parent-filename.scss {
|
@@ -134,23 +131,19 @@ module Sass
|
|
134
131
|
end
|
135
132
|
|
136
133
|
def test_call_embedded_importer
|
137
|
-
output = @embedded.render(
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
}
|
148
|
-
]
|
149
|
-
})[:css]
|
150
|
-
}
|
134
|
+
output = @embedded.render(data: "@import 'parent.scss';",
|
135
|
+
importer: [
|
136
|
+
lambda { |_url, _prev|
|
137
|
+
{
|
138
|
+
contents: @embedded.render(data: "@import 'parent-parent.scss'",
|
139
|
+
importer: [
|
140
|
+
lambda { |_url, _prev|
|
141
|
+
{ contents: 'h1 { color: black; }' }
|
142
|
+
}
|
143
|
+
])[:css]
|
151
144
|
}
|
152
|
-
|
153
|
-
|
145
|
+
}
|
146
|
+
])[:css]
|
154
147
|
|
155
148
|
assert_equal <<~CSS.chomp, output
|
156
149
|
h1 {
|
data/test/error_test.rb
CHANGED
@@ -20,10 +20,8 @@ module Sass
|
|
20
20
|
}
|
21
21
|
SCSS
|
22
22
|
|
23
|
-
@embedded.render(
|
24
|
-
|
25
|
-
})
|
26
|
-
rescue Sass::CompilationError => e
|
23
|
+
@embedded.render(data: template)
|
24
|
+
rescue RenderError => e
|
27
25
|
expected = 'stdin:3:20'
|
28
26
|
assert_equal expected, e.backtrace.first
|
29
27
|
end
|