sass-embedded 0.1.2 → 0.2.4

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: 8152265aec1c4adf4ec89b826191a97f202b14bf86cf4093e679f8c24cd2d5af
4
- data.tar.gz: 7b0d5d870ed38bf59562f617ad75cf2a3085704e0cc802989d21e15e9f14ee9b
3
+ metadata.gz: e345b143aa337d47b0f28d6d58d2a5636f84fb69b4f5ed739b6613ab39327a2d
4
+ data.tar.gz: 72c0bae0108414a5a989ea6b2f5eac86406eb83faa9c3fa1f1f642e609930afd
5
5
  SHA512:
6
- metadata.gz: 0d037a1fe518298f2976d99d947544642325fa763315e539115422fb78b0f185d35f6c3b97cb97ec373ae04b70984f772f3915ec35974a450095e1f5f3366d88
7
- data.tar.gz: 3844e6926954c302259f55a609e2e63a971c472580026ada05d13753dbebb4fb7bc4a13299c47b93a643b9fceaf742063e8a8b7b78f4ae541408eb4041e877b0
6
+ metadata.gz: 478a33de83120ace9665efd401d9988a956a291ed19b905f77d129486fd3e796bb90a66d6e3ee4f8f1fde47eddbb285918e84f5d8795d2a9f3546c82c431ef88
7
+ data.tar.gz: d9512132067039d511bafc684132ae010238b0b7b9ad1c37e3d7c6a4cb6463caa620eed87b05af3fc671c9200246f81af591b82d8c0d01b38429cafb87667394
@@ -27,16 +27,13 @@ jobs:
27
27
  bundler-cache: true
28
28
 
29
29
  - name: Download dart-sass-embedded
30
- run: bundle exec rake download
30
+ run: bundle exec rake extconf
31
31
  env:
32
32
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33
33
 
34
34
  - name: Test
35
35
  run: bundle exec rake
36
36
 
37
- - name: Build Gem
38
- run: rake build
39
-
40
37
  - name: Install Gem
41
38
  run: rake install
42
39
  env:
data/Gemfile CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
  gemspec
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Embedded Sass Host for Ruby
2
2
 
3
+ [![build](https://github.com/ntkme/embedded-host-ruby/actions/workflows/build.yml/badge.svg)](https://github.com/ntkme/embedded-host-ruby/actions/workflows/build.yml)
4
+
3
5
  This is a Ruby library that implements the host side of the [Embedded Sass protocol](https://github.com/sass/sass-embedded-protocol).
4
6
 
5
7
  It exposes a Ruby API for Sass that's backed by a native [Dart Sass](https://sass-lang.com/dart-sass) executable.
data/Rakefile CHANGED
@@ -1,14 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
 
3
5
  task default: :test
4
6
 
5
7
  desc 'Download dart-sass-embedded'
6
- task :download do
7
- require_relative 'ext/sass_embedded/extconf'
8
+ task :extconf do
9
+ system('make', '-C', 'ext', 'distclean')
10
+ require_relative 'ext/extconf'
11
+ system('make', '-C', 'ext')
8
12
  end
9
13
 
10
14
  desc 'Run all tests'
11
15
  task :test do
12
16
  $LOAD_PATH.unshift('lib', 'test')
13
- Dir.glob('./test/**/*_test.rb') { |f| require f }
17
+ Dir.glob('./test/**/*_test.rb').sort.each { |f| require f }
14
18
  end
File without changes
data/ext/Makefile ADDED
@@ -0,0 +1,51 @@
1
+ ifeq ($(OS),Windows_NT)
2
+ RM = cmd /c del /f /q /s
3
+ else
4
+ RM = rm -fr
5
+ endif
6
+ EXTCONF = ruby -- extconf.rb --without-sass-embedded --without-sass-embedded-protocol --without-protoc
7
+
8
+ .PHONY: all
9
+ all: sass_embedded embedded_sass_pb.rb
10
+
11
+ .PHONY: install
12
+ install:
13
+
14
+ .PHONY: clean
15
+ clean:
16
+ $(RM) embedded_sass_pb.rb protoc sass_embedded
17
+
18
+ .PHONY: distclean
19
+ distclean: clean
20
+ $(RM) embedded_sass.proto protoc-*.zip sass_embedded-*.tar.gz
21
+
22
+ ifeq ($(OS),Windows_NT)
23
+ sass_embedded-*.zip:
24
+ else
25
+ sass_embedded-*.tar.gz:
26
+ endif
27
+ $(EXTCONF) --with-sass-embedded
28
+
29
+ ifeq ($(OS),Windows_NT)
30
+ sass_embedded: sass_embedded-*.zip
31
+ powershell -c "Get-Item $< | Expand-Archive -DestinationPath . -Force"
32
+ else
33
+ sass_embedded: sass_embedded-*.tar.gz
34
+ tar -vxzf $<
35
+ endif
36
+
37
+ protoc-*.zip:
38
+ $(EXTCONF) --with-protoc
39
+
40
+ protoc: protoc-*.zip
41
+ ifeq ($(OS),Windows_NT)
42
+ powershell -c "Get-Item $< | Expand-Archive -DestinationPath $@ -Force"
43
+ else
44
+ unzip -od $@ $<
45
+ endif
46
+
47
+ embedded_sass.proto:
48
+ $(EXTCONF) --with-sass-embedded-protocol
49
+
50
+ %_pb.rb: %.proto protoc
51
+ ./protoc/bin/protoc --ruby_out=. $<
data/ext/extconf.rb ADDED
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'mkmf'
5
+ require 'json'
6
+ require 'open-uri'
7
+ require 'fileutils'
8
+ require_relative '../lib/sass/platform'
9
+
10
+ module Sass
11
+ class Extconf
12
+ def initialize
13
+ get_with_config('sass-embedded', true) { latest_sass_embedded }
14
+ get_with_config('sass-embedded-protocol', true) { latest_sass_embedded_protocol }
15
+ get_with_config('protoc', true) { latest_protoc }
16
+ end
17
+
18
+ private
19
+
20
+ def get_with_config(config, default)
21
+ val = with_config(config, default)
22
+ case val
23
+ when true
24
+ if block_given?
25
+ get yield
26
+ else
27
+ get default
28
+ end
29
+ when false
30
+ nil
31
+ else
32
+ get val
33
+ end
34
+ end
35
+
36
+ def get(uri_s)
37
+ uri = URI.parse(uri_s)
38
+ path = File.absolute_path(File.basename(uri.path), __dir__)
39
+ if uri.is_a?(URI::File) || uri.instance_of?(URI::Generic)
40
+ FileUtils.copy_file uri.path, path
41
+ elsif uri.respond_to? :open
42
+ uri.open do |source|
43
+ File.open(path, 'wb') do |destination|
44
+ destination.write source.read
45
+ end
46
+ end
47
+ else
48
+ raise
49
+ end
50
+ rescue StandardError
51
+ raise "Failed to get: #{uri}"
52
+ end
53
+
54
+ def latest_release(repo, prerelease: false)
55
+ if prerelease
56
+ headers = {}
57
+ headers['Authorization'] = "token #{ENV['GITHUB_TOKEN']}" if ENV['GITHUB_TOKEN']
58
+ URI.parse("https://api.github.com/repos/#{repo}/releases").open(headers) do |file|
59
+ JSON.parse(file.read)[0]['tag_name']
60
+ end
61
+ else
62
+ URI.parse("https://github.com/#{repo}/releases/latest").open do |file|
63
+ File.basename file.base_uri.to_s
64
+ end
65
+ end
66
+ end
67
+
68
+ def latest_sass_embedded
69
+ repo = 'sass/dart-sass-embedded'
70
+
71
+ # TODO, don't use prerelease once a release is available
72
+ tag_name = latest_release repo, prerelease: true
73
+
74
+ os = case Sass::Platform::OS
75
+ when 'darwin'
76
+ 'macos'
77
+ when 'linux'
78
+ 'linux'
79
+ when 'windows'
80
+ 'windows'
81
+ else
82
+ raise "Unsupported OS: #{Sass::Platform::OS}"
83
+ end
84
+
85
+ arch = case Sass::Platform::ARCH
86
+ when 'x86_64'
87
+ 'x64'
88
+ when 'i386'
89
+ 'ia32'
90
+ else
91
+ raise "Unsupported Arch: #{Sass::Platform::ARCH}"
92
+ end
93
+
94
+ ext = case os
95
+ when 'windows'
96
+ 'zip'
97
+ else
98
+ 'tar.gz'
99
+ end
100
+
101
+ "https://github.com/#{repo}/releases/download/#{tag_name}/sass_embedded-#{tag_name}-#{os}-#{arch}.#{ext}"
102
+ end
103
+
104
+ def latest_protoc
105
+ repo = 'protocolbuffers/protobuf'
106
+
107
+ tag_name = latest_release repo
108
+
109
+ os = case Sass::Platform::OS
110
+ when 'darwin'
111
+ 'osx'
112
+ when 'linux'
113
+ 'linux'
114
+ when 'windows'
115
+ 'win'
116
+ else
117
+ raise "Unsupported OS: #{Sass::Platform::OS}"
118
+ end
119
+
120
+ arch = case Sass::Platform::ARCH
121
+ when 'aarch64'
122
+ 'aarch_64'
123
+ when 'sparcv9'
124
+ 's390'
125
+ when 'i386'
126
+ 'x86_32'
127
+ when 'x86_64'
128
+ 'x86_64'
129
+ when 'powerpc64'
130
+ 'ppcle_64'
131
+ else
132
+ raise "Unsupported Arch: #{Sass::Platform::ARCH}"
133
+ end
134
+
135
+ os_arch = case os
136
+ when 'win'
137
+ os + arch.split('_').last
138
+ else
139
+ "#{os}-#{arch}"
140
+ end
141
+
142
+ ext = 'zip'
143
+
144
+ "https://github.com/#{repo}/releases/download/#{tag_name}/protoc-#{tag_name[1..-1]}-#{os_arch}.#{ext}"
145
+ end
146
+
147
+ def latest_sass_embedded_protocol
148
+ repo = 'sass/embedded-protocol'
149
+
150
+ # TODO: use latest release once available
151
+ # tag_name = latest_release repo
152
+ tag_name = 'HEAD'
153
+
154
+ "https://raw.githubusercontent.com/#{repo}/#{tag_name}/embedded_sass.proto"
155
+ end
156
+ end
157
+ end
158
+
159
+ Sass::Extconf.new
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::Compiler}. They are lower-precedence than any include
8
- # paths passed in via the `:include_paths` option.
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.
@@ -13,7 +13,7 @@ module Sass
13
13
  # (semicolon-separated on Windows).
14
14
  #
15
15
  # @example
16
- # Sass.include_paths << File.dirname(__FILE__ + '/sass')
16
+ # Sass.include_paths << File.dirname(__FILE__) + '/sass'
17
17
  # @return [Array<String, Pathname>]
18
18
  def self.include_paths
19
19
  @include_paths ||= if ENV['SASS_PATH']
@@ -23,15 +23,20 @@ module Sass
23
23
  end
24
24
  end
25
25
 
26
- def self.render options
27
- @compiler ||= Sass::Embedded::Compiler.new
28
- @compiler.render options
26
+ def self.render(options)
27
+ unless defined? @embedded
28
+ @embedded = Sass::Embedded.new
29
+ at_exit do
30
+ @embedded.close
31
+ end
32
+ end
33
+ @embedded.render options
29
34
  end
30
35
  end
31
36
 
32
- require_relative "sass/version"
33
- require_relative "sass/error"
34
- require_relative "sass/platform"
35
- require_relative "sass/util"
36
- require_relative "sass/embedded/transport"
37
- require_relative "sass/embedded/compiler"
37
+ require_relative 'sass/version'
38
+ require_relative 'sass/error'
39
+ require_relative 'sass/platform'
40
+ require_relative 'sass/util'
41
+ require_relative 'sass/transport'
42
+ require_relative 'sass/embedded'
@@ -0,0 +1,237 @@
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
+ nil
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