sass-embedded 0.1.3 → 0.2.1
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/lib/sass.rb +8 -8
- data/lib/sass/embedded.rb +236 -0
- data/lib/sass/transport.rb +138 -0
- data/lib/sass/version.rb +1 -1
- data/test/compiler_test.rb +13 -13
- data/test/custom_importer_test.rb +7 -7
- data/test/error_test.rb +3 -3
- data/test/functions_test.rb +6 -6
- data/test/output_style_test.rb +9 -9
- metadata +3 -3
- data/lib/sass/embedded/compiler.rb +0 -238
- data/lib/sass/embedded/transport.rb +0 -140
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edeb1d7a1a3ba0d1485671e579478b4b1bc5d372350a25869cd4b0305bd2f356
|
4
|
+
data.tar.gz: 9eda21b2bdb2d7f278d03c9e5091eeec13956d9e0216ebb4a415988f8ac90b62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
8
|
-
#
|
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? @
|
28
|
-
@
|
27
|
+
unless defined? @embedded
|
28
|
+
@embedded = Sass::Embedded.new
|
29
29
|
at_exit do
|
30
|
-
@
|
30
|
+
@embedded.close
|
31
31
|
end
|
32
32
|
end
|
33
|
-
@
|
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/
|
42
|
-
require_relative 'sass/embedded
|
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
data/test/compiler_test.rb
CHANGED
@@ -3,19 +3,19 @@
|
|
3
3
|
require_relative 'test_helper'
|
4
4
|
|
5
5
|
module Sass
|
6
|
-
class
|
6
|
+
class EmbeddedTest < MiniTest::Test
|
7
7
|
include TempFileTest
|
8
8
|
|
9
9
|
def setup
|
10
|
-
@
|
10
|
+
@embedded = Embedded.new
|
11
11
|
end
|
12
12
|
|
13
13
|
def teardown
|
14
|
-
@
|
14
|
+
@embedded.close
|
15
15
|
end
|
16
16
|
|
17
17
|
def render(data)
|
18
|
-
@
|
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 = @
|
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 = @
|
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 = @
|
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 = @
|
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}", @
|
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, @
|
219
|
+
assert_equal css, @embedded.render({ data: sass, indented_syntax: true })[:css]
|
220
220
|
assert_raises(CompilationError) do
|
221
|
-
@
|
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 = @
|
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 = @
|
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
|
-
@
|
10
|
+
@embedded = Embedded.new
|
11
11
|
end
|
12
12
|
|
13
13
|
def teardown
|
14
|
-
@
|
14
|
+
@embedded.close
|
15
15
|
end
|
16
16
|
|
17
17
|
def render(data, importer)
|
18
|
-
@
|
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 = @
|
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
|
137
|
-
output = @
|
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: @
|
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
|
-
@
|
8
|
+
@embedded = Embedded.new
|
9
9
|
end
|
10
10
|
|
11
11
|
def teardown
|
12
|
-
@
|
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
|
-
@
|
23
|
+
@embedded.render({
|
24
24
|
data: template
|
25
25
|
})
|
26
26
|
rescue Sass::CompilationError => e
|
data/test/functions_test.rb
CHANGED
@@ -5,15 +5,15 @@ require_relative 'test_helper'
|
|
5
5
|
module Sass
|
6
6
|
class FunctionsTest < MiniTest::Test
|
7
7
|
def setup
|
8
|
-
@
|
8
|
+
@embedded = Embedded.new
|
9
9
|
end
|
10
10
|
|
11
11
|
def teardown
|
12
|
-
@
|
12
|
+
@embedded.close
|
13
13
|
end
|
14
14
|
|
15
15
|
def render(sass)
|
16
|
-
@
|
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 = @
|
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 = @
|
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
|
-
@
|
368
|
+
@embedded.render({
|
369
369
|
data: 'div { url: test-function(); }',
|
370
370
|
functions: {
|
371
371
|
'test_function()': lambda {
|
data/test/output_style_test.rb
CHANGED
@@ -5,11 +5,11 @@ require_relative 'test_helper'
|
|
5
5
|
module Sass
|
6
6
|
class OutputStyleTest < MiniTest::Test
|
7
7
|
def setup
|
8
|
-
@
|
8
|
+
@embedded = Embedded.new
|
9
9
|
end
|
10
10
|
|
11
11
|
def teardown
|
12
|
-
@
|
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 = @
|
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 = @
|
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
|
-
@
|
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
|
-
@
|
71
|
+
@embedded.render({ data: input_scss, output_style: :nested })[:css]
|
72
72
|
end
|
73
73
|
|
74
74
|
assert_raises(UnsupportedValue) do
|
75
|
-
@
|
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 = @
|
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 = @
|
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
|
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
|
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
|