sass-embedded 0.9.0 → 0.10.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/LICENSE +1 -1
- data/README.md +3 -23
- data/ext/sass/extconf.rb +1 -3
- data/lib/sass/compile_error.rb +24 -0
- data/lib/sass/compile_result.rb +18 -0
- data/lib/sass/embedded/channel.rb +1 -1
- data/lib/sass/embedded/compile_context.rb +162 -137
- data/lib/sass/embedded/compiler/requirements.rb +1 -1
- data/lib/sass/embedded/compiler.rb +4 -4
- data/lib/sass/embedded/observer.rb +1 -0
- data/lib/sass/embedded/protocol_error.rb +7 -0
- data/lib/sass/embedded/render.rb +344 -0
- data/lib/sass/embedded/{util.rb → url.rb} +5 -13
- data/lib/sass/embedded/version.rb +1 -1
- data/lib/sass/embedded.rb +87 -210
- data/lib/sass/file_importer.rb +10 -0
- data/lib/sass/importer.rb +14 -0
- data/lib/sass/importer_result.rb +14 -0
- data/lib/sass/logger/source_location.rb +24 -0
- data/lib/sass/logger/source_span.rb +30 -0
- data/lib/sass/logger.rb +20 -0
- data/lib/sass.rb +40 -4
- metadata +26 -19
- data/lib/sass/embedded/error.rb +0 -29
- data/lib/sass/embedded/result.rb +0 -34
- data/lib/sass/embedded/struct.rb +0 -22
@@ -0,0 +1,344 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'json'
|
5
|
+
require 'pathname'
|
6
|
+
require_relative 'url'
|
7
|
+
|
8
|
+
module Sass
|
9
|
+
# The {Embedded} host for using dart-sass-embedded. Each instance creates
|
10
|
+
# its own {Channel}.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# embedded = Sass::Embedded.new
|
14
|
+
# result = embedded.compile_string('h1 { font-size: 40px; }')
|
15
|
+
# result = embedded.compile('style.scss')
|
16
|
+
# embedded.close
|
17
|
+
class Embedded
|
18
|
+
# @deprecated
|
19
|
+
def self.include_paths
|
20
|
+
@include_paths ||= if ENV['SASS_PATH']
|
21
|
+
ENV['SASS_PATH'].split(File::PATH_SEPARATOR)
|
22
|
+
else
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @deprecated
|
28
|
+
# The {Embedded#render} method.
|
29
|
+
#
|
30
|
+
# See {file:README.md#options} for supported options.
|
31
|
+
#
|
32
|
+
# @return [RenderResult]
|
33
|
+
# @raise [ProtocolError]
|
34
|
+
# @raise [RenderError]
|
35
|
+
def render(data: nil,
|
36
|
+
file: nil,
|
37
|
+
indented_syntax: false,
|
38
|
+
include_paths: [],
|
39
|
+
output_style: :expanded,
|
40
|
+
indent_type: :space,
|
41
|
+
indent_width: 2,
|
42
|
+
linefeed: :lf,
|
43
|
+
source_map: false,
|
44
|
+
out_file: nil,
|
45
|
+
omit_source_map_url: false,
|
46
|
+
source_map_contents: false,
|
47
|
+
source_map_embed: false,
|
48
|
+
source_map_root: '',
|
49
|
+
functions: {},
|
50
|
+
importer: [])
|
51
|
+
start = now
|
52
|
+
|
53
|
+
raise ArgumentError, 'either data or file must be set' if file.nil? && data.nil?
|
54
|
+
|
55
|
+
indent_type = parse_indent_type(indent_type)
|
56
|
+
indent_width = parse_indent_width(indent_width)
|
57
|
+
linefeed = parse_linefeed(linefeed)
|
58
|
+
|
59
|
+
load_paths = include_paths + Embedded.include_paths
|
60
|
+
|
61
|
+
source_map_option = source_map.is_a?(String) || (source_map == true && !out_file.nil?)
|
62
|
+
|
63
|
+
begin
|
64
|
+
compile_result = if data
|
65
|
+
compile_string(data, load_paths: load_paths,
|
66
|
+
syntax: indented_syntax ? :indented : :scss,
|
67
|
+
url: (Url.path_to_file_url(File.absolute_path(file)) unless file.nil?),
|
68
|
+
source_map: source_map_option,
|
69
|
+
style: output_style,
|
70
|
+
functions: functions,
|
71
|
+
importers: importer.map do |legacy_importer|
|
72
|
+
LegacyImporter.new(legacy_importer, file)
|
73
|
+
end)
|
74
|
+
else
|
75
|
+
compile(file, load_paths: load_paths,
|
76
|
+
source_map: source_map_option,
|
77
|
+
style: output_style,
|
78
|
+
functions: functions,
|
79
|
+
importers: importer.map do |legacy_importer|
|
80
|
+
LegacyImporter.new(legacy_importer, file)
|
81
|
+
end)
|
82
|
+
end
|
83
|
+
rescue CompileError => e
|
84
|
+
raise RenderError.new(
|
85
|
+
e.sass_message,
|
86
|
+
e.message,
|
87
|
+
if e.span.nil?
|
88
|
+
nil
|
89
|
+
elsif e.span.url.nil?
|
90
|
+
'stdin'
|
91
|
+
else
|
92
|
+
Url.file_url_to_path(e.span.url)
|
93
|
+
end,
|
94
|
+
e.span.start.line + 1,
|
95
|
+
e.span.start.column + 1,
|
96
|
+
1
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
map, source_map = post_process_map(map: compile_result.source_map,
|
101
|
+
data: data,
|
102
|
+
file: file,
|
103
|
+
out_file: out_file,
|
104
|
+
source_map: source_map,
|
105
|
+
source_map_contents: source_map_contents,
|
106
|
+
source_map_root: source_map_root)
|
107
|
+
|
108
|
+
css = post_process_css(css: compile_result.css,
|
109
|
+
indent_type: indent_type,
|
110
|
+
indent_width: indent_width,
|
111
|
+
linefeed: linefeed,
|
112
|
+
map: map,
|
113
|
+
out_file: out_file,
|
114
|
+
omit_source_map_url: omit_source_map_url,
|
115
|
+
source_map: source_map,
|
116
|
+
source_map_embed: source_map_embed)
|
117
|
+
|
118
|
+
finish = now
|
119
|
+
|
120
|
+
stats = RenderResultStats.new(file.nil? ? 'data' : file, start, finish, finish - start)
|
121
|
+
|
122
|
+
RenderResult.new(css, map, stats)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @deprecated
|
126
|
+
# The {RenderResult} of {Embedded#render}.
|
127
|
+
class RenderResult
|
128
|
+
attr_reader :css, :map, :stats
|
129
|
+
|
130
|
+
def initialize(css, map, stats)
|
131
|
+
@css = css
|
132
|
+
@map = map
|
133
|
+
@stats = stats
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# @deprecated
|
138
|
+
# The {RenderResultStats} of {Embedded#render}.
|
139
|
+
class RenderResultStats
|
140
|
+
attr_reader :entry, :start, :end, :duration
|
141
|
+
|
142
|
+
def initialize(entry, start, finish, duration)
|
143
|
+
@entry = entry
|
144
|
+
@start = start
|
145
|
+
@end = finish
|
146
|
+
@duration = duration
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# @deprecated
|
151
|
+
# The {RenderError} raised by {Embedded#render}.
|
152
|
+
class RenderError < StandardError
|
153
|
+
attr_reader :formatted, :file, :line, :column, :status
|
154
|
+
|
155
|
+
def initialize(message, formatted, file, line, column, status)
|
156
|
+
super(message)
|
157
|
+
@formatted = formatted
|
158
|
+
@file = file
|
159
|
+
@line = line
|
160
|
+
@column = column
|
161
|
+
@status = status
|
162
|
+
end
|
163
|
+
|
164
|
+
def backtrace
|
165
|
+
return nil if super.nil?
|
166
|
+
|
167
|
+
["#{@file}:#{@line}:#{@column}"] + super
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
# @deprecated
|
174
|
+
def post_process_map(map:,
|
175
|
+
data:,
|
176
|
+
file:,
|
177
|
+
out_file:,
|
178
|
+
source_map:,
|
179
|
+
source_map_contents:,
|
180
|
+
source_map_root:)
|
181
|
+
return if map.nil? || map.empty?
|
182
|
+
|
183
|
+
map_data = JSON.parse(map)
|
184
|
+
|
185
|
+
map_data['sourceRoot'] = source_map_root
|
186
|
+
|
187
|
+
source_map_path = if source_map.is_a? String
|
188
|
+
source_map
|
189
|
+
else
|
190
|
+
"#{out_file}.map"
|
191
|
+
end
|
192
|
+
|
193
|
+
source_map_dir = File.dirname(source_map_path)
|
194
|
+
|
195
|
+
if out_file
|
196
|
+
map_data['file'] = relative_path(source_map_dir, out_file)
|
197
|
+
elsif file
|
198
|
+
ext = File.extname(file)
|
199
|
+
map_data['file'] = "#{file[0..(ext.empty? ? -1 : -ext.length - 1)]}.css"
|
200
|
+
else
|
201
|
+
map_data['file'] = 'stdin.css'
|
202
|
+
end
|
203
|
+
|
204
|
+
map_data['sourcesContent'] = [] if source_map_contents
|
205
|
+
|
206
|
+
file = File.absolute_path(file) unless file.nil?
|
207
|
+
|
208
|
+
map_data['sources'].map! do |source|
|
209
|
+
if source.start_with? 'file://'
|
210
|
+
path = Url.file_url_to_path(source)
|
211
|
+
content = if path == file && !data.nil?
|
212
|
+
data
|
213
|
+
else
|
214
|
+
begin
|
215
|
+
File.read(path)
|
216
|
+
rescue StandardError
|
217
|
+
nil
|
218
|
+
end
|
219
|
+
end
|
220
|
+
map_data['sourcesContent'].push(content) if source_map_contents
|
221
|
+
relative_path(source_map_dir, path)
|
222
|
+
else
|
223
|
+
map_data['sourcesContent'].push(nil) if source_map_contents
|
224
|
+
source
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
[-JSON.generate(map_data), source_map_path]
|
229
|
+
end
|
230
|
+
|
231
|
+
# @deprecated
|
232
|
+
def post_process_css(css:,
|
233
|
+
indent_type:,
|
234
|
+
indent_width:,
|
235
|
+
linefeed:,
|
236
|
+
map:,
|
237
|
+
omit_source_map_url:,
|
238
|
+
out_file:,
|
239
|
+
source_map:,
|
240
|
+
source_map_embed:)
|
241
|
+
css = +css
|
242
|
+
if indent_width != 2 || indent_type.to_sym != :space
|
243
|
+
indent = indent_type * indent_width
|
244
|
+
css.gsub!(/^ +/) do |space|
|
245
|
+
indent * (space.length / 2)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
css.gsub!("\n", linefeed) if linefeed != "\n"
|
249
|
+
|
250
|
+
unless map.nil? || omit_source_map_url == true
|
251
|
+
url = if source_map_embed
|
252
|
+
"data:application/json;base64,#{Base64.strict_encode64(map)}"
|
253
|
+
elsif out_file
|
254
|
+
relative_path(File.dirname(out_file), source_map)
|
255
|
+
else
|
256
|
+
source_map
|
257
|
+
end
|
258
|
+
css += "#{linefeed}#{linefeed}/*# sourceMappingURL=#{url} */"
|
259
|
+
end
|
260
|
+
|
261
|
+
-css
|
262
|
+
end
|
263
|
+
|
264
|
+
# @deprecated
|
265
|
+
def parse_indent_type(indent_type)
|
266
|
+
case indent_type.to_sym
|
267
|
+
when :space
|
268
|
+
' '
|
269
|
+
when :tab
|
270
|
+
"\t"
|
271
|
+
else
|
272
|
+
raise ArgumentError, 'indent_type must be one of :space, :tab'
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# @deprecated
|
277
|
+
def parse_indent_width(indent_width)
|
278
|
+
raise ArgumentError, 'indent_width must be an integer' unless indent_width.is_a? Integer
|
279
|
+
raise RangeError, 'indent_width must be in between 0 and 10 (inclusive)' unless indent_width.between? 0, 10
|
280
|
+
|
281
|
+
indent_width
|
282
|
+
end
|
283
|
+
|
284
|
+
# @deprecated
|
285
|
+
def parse_linefeed(linefeed)
|
286
|
+
case linefeed.to_sym
|
287
|
+
when :lf
|
288
|
+
"\n"
|
289
|
+
when :lfcr
|
290
|
+
"\n\r"
|
291
|
+
when :cr
|
292
|
+
"\r"
|
293
|
+
when :crlf
|
294
|
+
"\r\n"
|
295
|
+
else
|
296
|
+
raise ArgumentError, 'linefeed must be one of :lf, :lfcr, :cr, :crlf'
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# @deprecated
|
301
|
+
def now
|
302
|
+
(Time.now.to_f * 1000).to_i
|
303
|
+
end
|
304
|
+
|
305
|
+
# @deprecated
|
306
|
+
def relative_path(from, to)
|
307
|
+
Pathname.new(File.absolute_path(to)).relative_path_from(Pathname.new(File.absolute_path(from))).to_s
|
308
|
+
end
|
309
|
+
|
310
|
+
# @deprecated
|
311
|
+
# The {LegacyImporter} for {Embedded#render}.
|
312
|
+
class LegacyImporter
|
313
|
+
def initialize(importer, file)
|
314
|
+
super()
|
315
|
+
@file = file
|
316
|
+
@importer = importer
|
317
|
+
@importer_results = {}
|
318
|
+
end
|
319
|
+
|
320
|
+
def canonicalize(url)
|
321
|
+
canonical_url = Url.path_to_file_url(File.absolute_path(url, (@file.nil? ? 'stdin' : @file)))
|
322
|
+
|
323
|
+
result = @importer.call canonical_url, @file
|
324
|
+
|
325
|
+
raise result if result.is_a? StandardError
|
326
|
+
|
327
|
+
if result&.key? :contents
|
328
|
+
@importer_results[canonical_url] = ImporterResult.new(result[:contents], :scss)
|
329
|
+
canonical_url
|
330
|
+
elsif result&.key? :file
|
331
|
+
canonical_url = Url.path_to_file_url(File.absolute_path(result[:file]))
|
332
|
+
@importer_results[canonical_url] = ImporterResult.new(File.read(result[:file]), :scss)
|
333
|
+
canonical_url
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def load(canonical_url)
|
338
|
+
@importer_results[canonical_url]
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
private_constant :LegacyImporter
|
343
|
+
end
|
344
|
+
end
|
@@ -1,33 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'pathname'
|
4
3
|
require 'uri'
|
5
4
|
|
6
5
|
module Sass
|
7
6
|
class Embedded
|
8
|
-
# The {
|
9
|
-
module
|
7
|
+
# The {Url} module.
|
8
|
+
module Url
|
9
|
+
# The {::URI::Parser} that is in consistent with Dart Uri.
|
10
10
|
URI_PARSER = URI::Parser.new({ RESERVED: ';/?:@&=+$,' })
|
11
11
|
|
12
12
|
private_constant :URI_PARSER
|
13
13
|
|
14
14
|
module_function
|
15
15
|
|
16
|
-
def
|
16
|
+
def path_to_file_url(path)
|
17
17
|
"file://#{Platform::OS == 'windows' ? File::SEPARATOR : ''}#{URI_PARSER.escape(path)}"
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
20
|
+
def file_url_to_path(file_uri)
|
21
21
|
URI_PARSER.unescape(file_uri[(Platform::OS == 'windows' ? 8 : 7)..])
|
22
22
|
end
|
23
|
-
|
24
|
-
def relative_path(from, to)
|
25
|
-
Pathname.new(File.absolute_path(to)).relative_path_from(Pathname.new(File.absolute_path(from))).to_s
|
26
|
-
end
|
27
|
-
|
28
|
-
def now
|
29
|
-
(Time.now.to_f * 1000).to_i
|
30
|
-
end
|
31
23
|
end
|
32
24
|
end
|
33
25
|
end
|