sass-embedded 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e345b143aa337d47b0f28d6d58d2a5636f84fb69b4f5ed739b6613ab39327a2d
4
- data.tar.gz: 72c0bae0108414a5a989ea6b2f5eac86406eb83faa9c3fa1f1f642e609930afd
3
+ metadata.gz: e03d2c1775fdd10b7f9d2b21898ac0a6e31e0be96157139742384c7be9b4275a
4
+ data.tar.gz: dacdc9589a8da93246b791794f97be2c8818f8a492d77e473c9fa543fd49db9e
5
5
  SHA512:
6
- metadata.gz: 478a33de83120ace9665efd401d9988a956a291ed19b905f77d129486fd3e796bb90a66d6e3ee4f8f1fde47eddbb285918e84f5d8795d2a9f3546c82c431ef88
7
- data.tar.gz: d9512132067039d511bafc684132ae010238b0b7b9ad1c37e3d7c6a4cb6463caa620eed87b05af3fc671c9200246f81af591b82d8c0d01b38429cafb87667394
6
+ metadata.gz: 3be32cd37a7078fceabd16c661cf985fdfc1aa31fbf8b88e6c768cb8edd57749a30e28a77f367d6cc8c6c505710f402e1970ae6c12210b3f46fc10a42cf8d81d
7
+ data.tar.gz: 5a122cd7c2e883f28b9401ce70d8fb43fcfcf39e1ff64484f5bf036c6978ca1a01cf8d69b1ebbf22f0fde81f98272127cf5a96d381b0279028d316d90775ebfb
@@ -41,4 +41,4 @@ jobs:
41
41
 
42
42
  - name: Test Gem
43
43
  run: |-
44
- ruby -r sass -e "puts Sass.render({ data: 'h1 { color: black; }' })[:css]"
44
+ ruby -r sass -e "puts Sass.render(data: 'h1 { color: black; }')[:css]"
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ AllCops:
2
+ Exclude:
3
+ - '**/*_pb.rb'
4
+ - 'vendor/**/*'
5
+
6
+ TargetRubyVersion: 2.6
7
+
8
+ NewCops: enable
9
+
10
+ Layout/LineLength:
11
+ Enabled: false
12
+
13
+ Metrics:
14
+ Enabled: false
data/README.md CHANGED
@@ -11,9 +11,7 @@ It exposes a Ruby API for Sass that's backed by a native [Dart Sass](https://sas
11
11
  ``` ruby
12
12
  require "sass"
13
13
 
14
- Sass.render({
15
- file: "style.scss"
16
- })
14
+ Sass.render(file: "style.scss")
17
15
  ```
18
16
 
19
17
  ---
data/Rakefile CHANGED
@@ -2,17 +2,22 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
 
5
- task default: :test
5
+ task default: %i[rubocop test]
6
6
 
7
7
  desc 'Download dart-sass-embedded'
8
8
  task :extconf do
9
- system('make', '-C', 'ext', 'distclean')
10
- require_relative 'ext/extconf'
11
9
  system('make', '-C', 'ext')
12
10
  end
13
11
 
14
12
  desc 'Run all tests'
15
- task :test do
13
+ task test: :extconf do
16
14
  $LOAD_PATH.unshift('lib', 'test')
17
15
  Dir.glob('./test/**/*_test.rb').sort.each { |f| require f }
18
16
  end
17
+
18
+ begin
19
+ require 'rubocop/rake_task'
20
+ RuboCop::RakeTask.new
21
+ rescue LoadError
22
+ nil
23
+ end
data/ext/extconf.rb CHANGED
@@ -8,6 +8,7 @@ require 'fileutils'
8
8
  require_relative '../lib/sass/platform'
9
9
 
10
10
  module Sass
11
+ # Install dependencies for sass-embedded during gem install
11
12
  class Extconf
12
13
  def initialize
13
14
  get_with_config('sass-embedded', true) { latest_sass_embedded }
@@ -141,7 +142,7 @@ module Sass
141
142
 
142
143
  ext = 'zip'
143
144
 
144
- "https://github.com/#{repo}/releases/download/#{tag_name}/protoc-#{tag_name[1..-1]}-#{os_arch}.#{ext}"
145
+ "https://github.com/#{repo}/releases/download/#{tag_name}/protoc-#{tag_name[1..]}-#{os_arch}.#{ext}"
145
146
  end
146
147
 
147
148
  def latest_sass_embedded_protocol
data/lib/sass.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # The Sass module
3
4
  module Sass
4
5
  # The global include_paths for Sass files. This is meant for plugins and
5
6
  # libraries to register the paths to their Sass stylesheets to that they may
@@ -23,14 +24,21 @@ module Sass
23
24
  end
24
25
  end
25
26
 
26
- def self.render(options)
27
+ # The global render methods. This method automatically instantiates a
28
+ # global {Sass::Embedded} instance when invoked the first time and call
29
+ # `:render` method on the instance thereafter. The global {Sass::Embedded}
30
+ # is automatically closed via {Kernel.at_exit}.
31
+ # @example
32
+ # Sass.render(options)
33
+ # @return [Hash]
34
+ def self.render(**kwargs)
27
35
  unless defined? @embedded
28
36
  @embedded = Sass::Embedded.new
29
37
  at_exit do
30
38
  @embedded.close
31
39
  end
32
40
  end
33
- @embedded.render options
41
+ @embedded.render(**kwargs)
34
42
  end
35
43
  end
36
44
 
data/lib/sass/embedded.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sass
4
+ # The interface for using dart-sass-embedded
4
5
  class Embedded
5
6
  def initialize
6
7
  @transport = Transport.new
@@ -8,198 +9,83 @@ module Sass
8
9
  @id = 0
9
10
  end
10
11
 
11
- def render(options)
12
- start = Sass::Util.now
12
+ # rubocop:disable Lint/UnusedMethodArgument
13
13
 
14
- raise Sass::NotRenderedError, 'Either :data or :file must be set.' if options[:file].nil? && options[:data].nil?
14
+ def render(data: nil,
15
+ file: nil,
16
+ indented_syntax: false,
17
+ include_paths: [],
18
+ output_style: :expanded,
19
+ precision: 5,
20
+ indent_type: :space,
21
+ indent_width: 2,
22
+ linefeed: :lf,
23
+ source_comments: false,
24
+ source_map: false,
25
+ out_file: nil,
26
+ omit_source_map_url: false,
27
+ source_map_contents: false,
28
+ source_map_embed: false,
29
+ source_map_root: '',
30
+ functions: {},
31
+ importer: [])
32
+ # rubocop:enable Lint/UnusedMethodArgument
15
33
 
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
34
+ start = Util.now
65
35
 
66
36
  compilation_id = next_id
67
37
 
68
- compile_request = Sass::EmbeddedProtocol::InboundMessage::CompileRequest.new(
69
- id: compilation_id,
70
- string: string,
71
- path: path,
72
- style: style,
38
+ renderer = Renderer.new(
39
+ data: data,
40
+ file: file,
41
+ indented_syntax: indented_syntax,
42
+ include_paths: include_paths,
43
+ output_style: output_style,
73
44
  source_map: source_map,
74
- importers: importers,
75
- global_functions: options[:functions] ? signatures : [],
76
- alert_color: true,
77
- alert_ascii: true
45
+ out_file: out_file,
46
+ functions: functions,
47
+ importer: importer
78
48
  )
79
49
 
80
- response = @transport.send compile_request, compilation_id
81
-
82
- file = options[:file] || 'stdin'
83
- canonicalizations = {}
84
- imports = {}
50
+ response = @transport.send renderer.compile_request(compilation_id), compilation_id
85
51
 
86
52
  loop do
87
53
  case response
88
- when Sass::EmbeddedProtocol::OutboundMessage::CompileResponse
54
+ when EmbeddedProtocol::OutboundMessage::CompileResponse
89
55
  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
56
+ when EmbeddedProtocol::OutboundMessage::CanonicalizeRequest
57
+ response = @transport.send renderer.canonicalize_response(response), compilation_id
58
+ when EmbeddedProtocol::OutboundMessage::ImportRequest
59
+ response = @transport.send renderer.import_response(response), compilation_id
60
+ when EmbeddedProtocol::OutboundMessage::FunctionCallRequest
61
+ response = @transport.send renderer.function_call_response(response), compilation_id
62
+ when EmbeddedProtocol::ProtocolError
63
+ raise ProtocolError, response.message
180
64
  else
181
- raise Sass::ProtocolError, "Unexpected packet received: #{response}"
65
+ raise ProtocolError, "Unexpected packet received: #{response}"
182
66
  end
183
67
  end
184
68
 
185
69
  if response.failure
186
- raise Sass::CompilationError.new(
70
+ raise RenderError.new(
187
71
  response.failure.message,
188
72
  response.failure.formatted,
189
- response.failure.span ? response.failure.span.url : nil,
73
+ if response.failure.span
74
+ response.failure.span.url == '' ? 'stdin' : URI.parse(response.failure.span.url).path
75
+ end,
190
76
  response.failure.span ? response.failure.span.start.line + 1 : nil,
191
77
  response.failure.span ? response.failure.span.start.column + 1 : nil,
192
78
  1
193
79
  )
194
80
  end
195
81
 
196
- finish = Sass::Util.now
82
+ finish = Util.now
197
83
 
198
84
  {
199
85
  css: response.success.css,
200
86
  map: response.success.source_map,
201
87
  stats: {
202
- entry: options[:file] || 'data',
88
+ entry: file.nil? ? 'data' : file,
203
89
  start: start,
204
90
  end: finish,
205
91
  duration: finish - start
@@ -215,7 +101,7 @@ module Sass
215
101
  private
216
102
 
217
103
  def info
218
- version_response = @transport.send Sass::EmbeddedProtocol::InboundMessage::VersionRequest.new(
104
+ version_response = @transport.send EmbeddedProtocol::InboundMessage::VersionRequest.new(
219
105
  id: next_id
220
106
  )
221
107
  {
@@ -233,5 +119,199 @@ module Sass
233
119
  @id
234
120
  end
235
121
  end
122
+
123
+ # Helper class that maintains render state
124
+ class Renderer
125
+ def initialize(data:,
126
+ file:,
127
+ indented_syntax:,
128
+ include_paths:,
129
+ output_style:,
130
+ source_map:,
131
+ out_file:,
132
+ functions:,
133
+ importer:)
134
+ raise ArgumentError, 'Either :data or :file must be set.' if file.nil? && data.nil?
135
+
136
+ @data = data
137
+ @file = file
138
+ @indented_syntax = indented_syntax
139
+ @include_paths = include_paths
140
+ @output_style = output_style
141
+ @source_map = source_map
142
+ @out_file = out_file
143
+ @global_functions = functions.keys
144
+ @functions = functions.transform_keys do |key|
145
+ key.to_s.split('(')[0].chomp
146
+ end
147
+ @importer = importer
148
+ @import_responses = {}
149
+ end
150
+
151
+ def compile_request(id)
152
+ EmbeddedProtocol::InboundMessage::CompileRequest.new(
153
+ id: id,
154
+ string: string,
155
+ path: path,
156
+ style: style,
157
+ source_map: source_map,
158
+ importers: importers,
159
+ global_functions: global_functions,
160
+ alert_color: $stderr.tty?,
161
+ alert_ascii: Platform::OS == 'windows'
162
+ )
163
+ end
164
+
165
+ def canonicalize_response(canonicalize_request)
166
+ url = Util.file_uri(File.absolute_path(canonicalize_request.url, (@file.nil? ? 'stdin' : @file)))
167
+
168
+ begin
169
+ result = @importer[canonicalize_request.importer_id].call canonicalize_request.url, @file
170
+ raise result if result.is_a? StandardError
171
+ rescue StandardError => e
172
+ return EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
173
+ id: canonicalize_request.id,
174
+ error: e.message
175
+ )
176
+ end
177
+
178
+ if result&.key? :contents
179
+ @import_responses[url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
180
+ id: canonicalize_request.id,
181
+ success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
182
+ contents: result[:contents],
183
+ syntax: EmbeddedProtocol::Syntax::SCSS,
184
+ source_map_url: nil
185
+ )
186
+ )
187
+ EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
188
+ id: canonicalize_request.id,
189
+ url: url
190
+ )
191
+ elsif result&.key? :file
192
+ canonicalized_url = Util.file_uri(result[:file])
193
+
194
+ # TODO: FileImportRequest is not supported yet.
195
+ # Workaround by reading contents and return it when server asks
196
+ @import_responses[canonicalized_url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
197
+ id: canonicalize_request.id,
198
+ success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
199
+ contents: File.read(result[:file]),
200
+ syntax: EmbeddedProtocol::Syntax::SCSS,
201
+ source_map_url: nil
202
+ )
203
+ )
204
+
205
+ EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
206
+ id: canonicalize_request.id,
207
+ url: canonicalized_url
208
+ )
209
+ else
210
+ EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
211
+ id: canonicalize_request.id
212
+ )
213
+ end
214
+ end
215
+
216
+ def import_response(import_request)
217
+ url = import_request.url
218
+
219
+ if @import_responses.key? url
220
+ @import_responses[url].id = import_request.id
221
+ else
222
+ @import_responses[url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
223
+ id: import_request.id,
224
+ error: "Failed to import: #{url}"
225
+ )
226
+ end
227
+
228
+ @import_responses[url]
229
+ end
230
+
231
+ def function_call_response(function_call_request)
232
+ EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
233
+ id: function_call_request.id,
234
+ success: @functions[function_call_request.name].call(*function_call_request.arguments)
235
+ )
236
+ rescue StandardError => e
237
+ EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
238
+ id: function_call_request.id,
239
+ error: e.message
240
+ )
241
+ end
242
+
243
+ private
244
+
245
+ def syntax
246
+ if @indented_syntax == true
247
+ EmbeddedProtocol::Syntax::INDENTED
248
+ else
249
+ EmbeddedProtocol::Syntax::SCSS
250
+ end
251
+ end
252
+
253
+ def url
254
+ return if @file.nil?
255
+
256
+ Util.file_uri(@file)
257
+ end
258
+
259
+ def string
260
+ return if @data.nil?
261
+
262
+ EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
263
+ source: @data,
264
+ url: url,
265
+ syntax: syntax
266
+ )
267
+ end
268
+
269
+ def path
270
+ @file if @data.nil?
271
+ end
272
+
273
+ def style
274
+ case @output_style.to_sym
275
+ when :expanded
276
+ EmbeddedProtocol::OutputStyle::EXPANDED
277
+ when :compressed
278
+ EmbeddedProtocol::OutputStyle::COMPRESSED
279
+ when :nested, :compact
280
+ raise ArgumentError, "#{@output_style} is not a supported output_style"
281
+ else
282
+ raise ArgumentError, "#{@output_style} is not a valid utput_style"
283
+ end
284
+ end
285
+
286
+ def source_map
287
+ @source_map.is_a?(String) || (@source_map == true && !@out_file.nil?)
288
+ end
289
+
290
+ attr_reader :global_functions
291
+
292
+ # Order
293
+ # 1. Loading a file relative to the file in which the @use or @import appeared.
294
+ # 2. Each custom importer.
295
+ # 3. Loading a file relative to the current working directory.
296
+ # 4. Each load path in includePaths
297
+ # 5. Each load path specified in the SASS_PATH environment variable, which should be semicolon-separated on Windows and colon-separated elsewhere.
298
+ def importers
299
+ custom_importers = @importer.map.with_index do |_, id|
300
+ EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
301
+ importer_id: id
302
+ )
303
+ end
304
+
305
+ include_path_importers = @include_paths
306
+ .concat(Sass.include_paths)
307
+ .map do |include_path|
308
+ EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
309
+ path: File.absolute_path(include_path)
310
+ )
311
+ end
312
+
313
+ custom_importers.concat include_path_importers
314
+ end
315
+ end
236
316
  end
237
317
  end