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 +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
|