sass-embedded 0.9.3 → 0.13.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.
@@ -0,0 +1,328 @@
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
+ source_map_include_sources: source_map_contents,
70
+ style: output_style,
71
+ functions: functions,
72
+ importers: importer.map do |legacy_importer|
73
+ LegacyImporter.new(legacy_importer, file)
74
+ end)
75
+ else
76
+ compile(file, load_paths: load_paths,
77
+ source_map: source_map_option,
78
+ source_map_include_sources: source_map_contents,
79
+ style: output_style,
80
+ functions: functions,
81
+ importers: importer.map do |legacy_importer|
82
+ LegacyImporter.new(legacy_importer, file)
83
+ end)
84
+ end
85
+ rescue CompileError => e
86
+ raise RenderError.new(
87
+ e.sass_message,
88
+ e.message,
89
+ if e.span.nil?
90
+ nil
91
+ elsif e.span.url.nil?
92
+ 'stdin'
93
+ else
94
+ Url.file_url_to_path(e.span.url)
95
+ end,
96
+ e.span.start.line + 1,
97
+ e.span.start.column + 1,
98
+ 1
99
+ )
100
+ end
101
+
102
+ map, source_map = post_process_map(map: compile_result.source_map,
103
+ file: file,
104
+ out_file: out_file,
105
+ source_map: source_map,
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
+ file:,
176
+ out_file:,
177
+ source_map:,
178
+ source_map_root:)
179
+ return if map.nil? || map.empty?
180
+
181
+ map_data = JSON.parse(map)
182
+
183
+ map_data['sourceRoot'] = source_map_root
184
+
185
+ source_map_path = if source_map.is_a? String
186
+ source_map
187
+ else
188
+ "#{out_file}.map"
189
+ end
190
+
191
+ source_map_dir = File.dirname(source_map_path)
192
+
193
+ if out_file
194
+ map_data['file'] = relative_path(source_map_dir, out_file)
195
+ elsif file
196
+ ext = File.extname(file)
197
+ map_data['file'] = "#{file[0..(ext.empty? ? -1 : -ext.length - 1)]}.css"
198
+ else
199
+ map_data['file'] = 'stdin.css'
200
+ end
201
+
202
+ map_data['sources'].map! do |source|
203
+ if source.start_with? 'file://'
204
+ path = Url.file_url_to_path(source)
205
+ relative_path(source_map_dir, path)
206
+ else
207
+ source
208
+ end
209
+ end
210
+
211
+ [-JSON.generate(map_data), source_map_path]
212
+ end
213
+
214
+ # @deprecated
215
+ def post_process_css(css:,
216
+ indent_type:,
217
+ indent_width:,
218
+ linefeed:,
219
+ map:,
220
+ omit_source_map_url:,
221
+ out_file:,
222
+ source_map:,
223
+ source_map_embed:)
224
+ css = +css
225
+ if indent_width != 2 || indent_type.to_sym != :space
226
+ indent = indent_type * indent_width
227
+ css.gsub!(/^ +/) do |space|
228
+ indent * (space.length / 2)
229
+ end
230
+ end
231
+ css.gsub!("\n", linefeed) if linefeed != "\n"
232
+
233
+ unless map.nil? || omit_source_map_url == true
234
+ url = if source_map_embed
235
+ "data:application/json;base64,#{Base64.strict_encode64(map)}"
236
+ elsif out_file
237
+ relative_path(File.dirname(out_file), source_map)
238
+ else
239
+ source_map
240
+ end
241
+ css += "#{linefeed}#{linefeed}/*# sourceMappingURL=#{url} */"
242
+ end
243
+
244
+ -css
245
+ end
246
+
247
+ # @deprecated
248
+ def parse_indent_type(indent_type)
249
+ case indent_type.to_sym
250
+ when :space
251
+ ' '
252
+ when :tab
253
+ "\t"
254
+ else
255
+ raise ArgumentError, 'indent_type must be one of :space, :tab'
256
+ end
257
+ end
258
+
259
+ # @deprecated
260
+ def parse_indent_width(indent_width)
261
+ raise ArgumentError, 'indent_width must be an integer' unless indent_width.is_a? Integer
262
+ raise RangeError, 'indent_width must be in between 0 and 10 (inclusive)' unless indent_width.between? 0, 10
263
+
264
+ indent_width
265
+ end
266
+
267
+ # @deprecated
268
+ def parse_linefeed(linefeed)
269
+ case linefeed.to_sym
270
+ when :lf
271
+ "\n"
272
+ when :lfcr
273
+ "\n\r"
274
+ when :cr
275
+ "\r"
276
+ when :crlf
277
+ "\r\n"
278
+ else
279
+ raise ArgumentError, 'linefeed must be one of :lf, :lfcr, :cr, :crlf'
280
+ end
281
+ end
282
+
283
+ # @deprecated
284
+ def now
285
+ (Time.now.to_f * 1000).to_i
286
+ end
287
+
288
+ # @deprecated
289
+ def relative_path(from, to)
290
+ Pathname.new(File.absolute_path(to)).relative_path_from(Pathname.new(File.absolute_path(from))).to_s
291
+ end
292
+
293
+ # @deprecated
294
+ # The {LegacyImporter} for {Embedded#render}.
295
+ class LegacyImporter
296
+ def initialize(importer, file)
297
+ super()
298
+ @file = file
299
+ @importer = importer
300
+ @importer_results = {}
301
+ end
302
+
303
+ def canonicalize(url, **_kwargs)
304
+ path = Url.file_url_to_path(url)
305
+ canonical_url = Url.path_to_file_url(File.absolute_path(path, (@file.nil? ? 'stdin' : @file)))
306
+
307
+ result = @importer.call canonical_url, @file
308
+
309
+ raise result if result.is_a? StandardError
310
+
311
+ if result&.key? :contents
312
+ @importer_results[canonical_url] = ImporterResult.new(result[:contents], :scss)
313
+ canonical_url
314
+ elsif result&.key? :file
315
+ canonical_url = Url.path_to_file_url(File.absolute_path(result[:file]))
316
+ @importer_results[canonical_url] = ImporterResult.new(File.read(result[:file]), :scss)
317
+ canonical_url
318
+ end
319
+ end
320
+
321
+ def load(canonical_url)
322
+ @importer_results[canonical_url]
323
+ end
324
+ end
325
+
326
+ private_constant :LegacyImporter
327
+ end
328
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module Sass
6
+ class Embedded
7
+ # The {Url} module.
8
+ module Url
9
+ # The {::URI::Parser} that is in consistent with RFC 2396 (URI Generic Syntax) and dart:core library.
10
+ URI_PARSER = URI::Parser.new({ RESERVED: ';/?:@&=+$,' })
11
+
12
+ FILE_SCHEME = 'file://'
13
+
14
+ private_constant :URI_PARSER, :FILE_SCHEME
15
+
16
+ module_function
17
+
18
+ def path_to_file_url(path)
19
+ if File.absolute_path? path
20
+ URI_PARSER.escape "#{FILE_SCHEME}#{Platform::OS == 'windows' ? File::SEPARATOR : ''}#{path}"
21
+ else
22
+ URI_PARSER.escape path
23
+ end
24
+ end
25
+
26
+ def file_url_to_path(url)
27
+ if url.start_with? FILE_SCHEME
28
+ URI_PARSER.unescape url[(FILE_SCHEME.length + (Platform::OS == 'windows' ? 1 : 0))..]
29
+ else
30
+ URI_PARSER.unescape url
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sass
4
4
  class Embedded
5
- VERSION = '0.9.3'
5
+ VERSION = '0.13.0'
6
6
  end
7
7
  end