sass-embedded 0.1.0 → 0.2.2

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: 7d0da205f0cb44177b737a82ce4addac1c94afe4da4a43327963d11feda1fe75
4
- data.tar.gz: 9821f8807b4eb6b0c612391628d341d416f96df3d3344eeaa762d88d8bbb9f6d
3
+ metadata.gz: 6c14e0ab686080f404d76337be0b59768bb600c48850f817c168cf53929239d9
4
+ data.tar.gz: e707aa4c0e910e71d0a316e4d3f1609b4c3cc49e49107c42af31d1ba3f9d3b62
5
5
  SHA512:
6
- metadata.gz: ef2437a502053cdae299f97e9e96b28402d9cdb07c8c40454f9666ccdbe66fc7aa20c17fe629978021e3c4a86d91512c112e04b85a144604fd7ba79992081663
7
- data.tar.gz: af805efa9a30ca83af3e152cb30b0a72649071e9fd2d2f08ea968a4eedab170c02367c2a5afed84156b4c29e71294e8b9be3bb79371ef8388436fe71adb8ea2e
6
+ metadata.gz: c549da93d4a68fb330b1019c462c593d18630c028a7a3add53b13dc72f18918d0f7e815c5abc0612584e749a87752c0c56c71c43fbb477829d8063cdfbb16fa3
7
+ data.tar.gz: 0c0af0ff5e8d40ac6f4305eb04b268aaccb154526071ab5799f4dfdb148ef7cba1c39ff45c9eb5b274d7a9d5a6170fefaf60be86fdf8ce127d1167799e85da47
@@ -34,9 +34,6 @@ jobs:
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
 
3
5
  task default: :test
@@ -10,5 +12,5 @@ end
10
12
  desc 'Run all tests'
11
13
  task :test do
12
14
  $LOAD_PATH.unshift('lib', 'test')
13
- Dir.glob('./test/**/*_test.rb') { |f| require f }
15
+ Dir.glob('./test/**/*_test.rb').sort.each { |f| require f }
14
16
  end
@@ -1,2 +1,3 @@
1
+ /embedded_sass*
1
2
  /sass_embedded*
2
- /embedded_sass_pb.*
3
+ /protoc*
@@ -1,23 +1,35 @@
1
1
  .PHONY: install
2
2
  install:
3
3
  ifeq ($(OS),Windows_NT)
4
- powershell -command "Get-Item *.zip | Expand-Archive -DestinationPath (Get-Location) -Force"
4
+ powershell -command "Get-Item sass_embedded-*.zip | Expand-Archive -DestinationPath (Get-Location) -Force"
5
+ powershell -command "Get-Item protoc-*.zip | Expand-Archive -DestinationPath protoc -Force"
6
+ ./protoc/bin/protoc --ruby_out=. embedded_sass.proto
5
7
  else
6
- tar -vxzf *.tar.gz
8
+ tar -vxzf sass_embedded-*.tar.gz
9
+ unzip -od protoc protoc-*.zip
10
+ ./protoc/bin/protoc --ruby_out=. embedded_sass.proto
7
11
  endif
8
12
 
9
13
  .PHONY: clean
10
14
  clean:
11
15
  ifeq ($(OS),Windows_NT)
12
16
  powershell -command "Remove-Item sass_embedded -Recurse -Force -ErrorAction Ignore"
17
+ powershell -command "Remove-Item protoc -Recurse -Force -ErrorAction Ignore"
18
+ powershell -command "Remove-Item embedded_sass_pb.rb -Recurse -Force -ErrorAction Ignore"
13
19
  else
14
20
  rm -rf sass_embedded
21
+ rm -rf protoc
22
+ rm -rf embedded_sass_pb.rb
15
23
  endif
16
24
 
17
25
  .PHONY: distclean
18
26
  distclean: clean
19
27
  ifeq ($(OS),Windows_NT)
20
28
  powershell -command "Remove-Item sass_embedded-*.zip -Force -ErrorAction Ignore"
29
+ powershell -command "Remove-Item protoc-*.zip -Force -ErrorAction Ignore"
30
+ powershell -command "Remove-Item embedded_sass.proto -Force -ErrorAction Ignore"
21
31
  else
22
32
  rm -rf sass_embedded-*.tar.gz
33
+ rm -rf protoc-*.zip
34
+ rm -rf embedded_sass.proto
23
35
  endif
@@ -1,72 +1,136 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "mkmf"
4
- require "json"
5
- require "open-uri"
6
- require_relative "../../lib/sass/platform"
4
+ require 'mkmf'
5
+ require 'json'
6
+ require 'open-uri'
7
+ require_relative '../../lib/sass/platform'
7
8
 
8
- def api url
9
- headers = {}
10
- headers["Authorization"] = "token #{ENV["GITHUB_TOKEN"]}" if ENV["GITHUB_TOKEN"]
11
- URI.open(url, headers) do |file|
12
- JSON.parse file.read
13
- end
14
- end
9
+ module Sass
10
+ class Extconf
11
+
12
+ def initialize
13
+ system('make', '-C', __dir__, 'distclean')
14
+ download_sass_embedded
15
+ download_protoc
16
+ download_embedded_sass_proto
17
+ system('make', '-C', __dir__, 'install')
18
+
19
+ File.open(File.absolute_path("sass_embedded.#{RbConfig::CONFIG['DLEXT']}", __dir__), 'w') {}
15
20
 
16
- def download url
17
- URI.open(url) do |source|
18
- File.open(File.absolute_path(File.basename(url), __dir__), "wb") do |destination|
19
- destination.write source.read
21
+ $makefile_created = true
20
22
  end
21
- end
22
- end
23
23
 
24
- def download_sass_embedded
25
- repo = "sass/dart-sass-embedded"
26
-
27
- release = api("https://api.github.com/repos/#{repo}/releases")[0]['tag_name']
28
-
29
- os = case Sass::Platform::OS
30
- when "darwin"
31
- "macos"
32
- when "linux"
33
- "linux"
34
- when "windows"
35
- "windows"
36
- else
37
- raise "Unsupported OS: #{Sass::Platform::OS}"
38
- end
39
-
40
- arch = case Sass::Platform::ARCH
41
- when "x86_64"
42
- "x64"
43
- when "i386"
44
- "ia32"
45
- else
46
- raise "Unsupported Arch: #{Sass::Platform::ARCH}"
47
- end
48
-
49
-
50
- ext = case os
51
- when "windows"
52
- "zip"
53
- else
54
- "tar.gz"
24
+ private
25
+
26
+ def api(url)
27
+ headers = {}
28
+ headers['Authorization'] = "token #{ENV['GITHUB_TOKEN']}" if ENV['GITHUB_TOKEN']
29
+ URI.parse(url).open(headers) do |file|
30
+ JSON.parse file.read
31
+ end
32
+ end
33
+
34
+ def download(url)
35
+ URI.parse(url).open do |source|
36
+ File.open(File.absolute_path(File.basename(url), __dir__), 'wb') do |destination|
37
+ destination.write source.read
55
38
  end
39
+ end
40
+ rescue StandardError
41
+ raise "Failed to download: #{url}"
42
+ end
56
43
 
57
- url = "https://github.com/#{repo}/releases/download/#{release}/sass_embedded-#{release}-#{os}-#{arch}.#{ext}"
44
+ def download_sass_embedded
45
+ repo = 'sass/dart-sass-embedded'
58
46
 
59
- begin
60
- download url
61
- rescue
62
- raise "Failed to download: #{url}"
63
- end
64
- end
47
+ release = api("https://api.github.com/repos/#{repo}/releases")[0]['tag_name']
65
48
 
66
- system("make", "-C", __dir__, "distclean")
67
- download_sass_embedded
68
- system("make", "-C", __dir__, "install")
49
+ os = case Sass::Platform::OS
50
+ when 'darwin'
51
+ 'macos'
52
+ when 'linux'
53
+ 'linux'
54
+ when 'windows'
55
+ 'windows'
56
+ else
57
+ raise "Unsupported OS: #{Sass::Platform::OS}"
58
+ end
69
59
 
70
- File.open(File.absolute_path("sass_embedded.#{RbConfig::CONFIG['DLEXT']}", __dir__), "w") {}
60
+ arch = case Sass::Platform::ARCH
61
+ when 'x86_64'
62
+ 'x64'
63
+ when 'i386'
64
+ 'ia32'
65
+ else
66
+ raise "Unsupported Arch: #{Sass::Platform::ARCH}"
67
+ end
68
+
69
+ ext = case os
70
+ when 'windows'
71
+ 'zip'
72
+ else
73
+ 'tar.gz'
74
+ end
75
+
76
+ url = "https://github.com/#{repo}/releases/download/#{release}/sass_embedded-#{release}-#{os}-#{arch}.#{ext}"
77
+ download url
78
+ end
79
+
80
+ def download_protoc
81
+ repo = 'protocolbuffers/protobuf'
82
+
83
+ tag = URI.parse("https://github.com/#{repo}/releases/latest").open do |file|
84
+ File.basename file.base_uri.to_s
85
+ end
86
+
87
+ release = tag[1..-1]
88
+
89
+ os = case Sass::Platform::OS
90
+ when 'darwin'
91
+ 'osx'
92
+ when 'linux'
93
+ 'linux'
94
+ when 'windows'
95
+ 'win'
96
+ else
97
+ raise "Unsupported OS: #{Sass::Platform::OS}"
98
+ end
99
+
100
+ arch = case Sass::Platform::ARCH
101
+ when 'aarch64'
102
+ 'aarch_64'
103
+ when 'sparcv9'
104
+ 's390'
105
+ when 'i386'
106
+ 'x86_32'
107
+ when 'x86_64'
108
+ 'x86_64'
109
+ when 'powerpc64'
110
+ 'ppcle_64'
111
+ else
112
+ raise "Unsupported Arch: #{Sass::Platform::ARCH}"
113
+ end
114
+
115
+ os_arch = case os
116
+ when 'win'
117
+ os + arch.split('_').last
118
+ else
119
+ "#{os}-#{arch}"
120
+ end
121
+
122
+ ext = 'zip'
123
+
124
+ url = "https://github.com/#{repo}/releases/download/#{tag}/protoc-#{release}-#{os_arch}.#{ext}"
125
+ download url
126
+ end
127
+
128
+ def download_embedded_sass_proto
129
+ url = 'https://raw.githubusercontent.com/sass/embedded-protocol/HEAD/embedded_sass.proto'
130
+ download url
131
+ end
132
+
133
+ end
134
+ end
71
135
 
72
- $makefile_created = true
136
+ Sass::Extconf.new
data/lib/sass.rb CHANGED
@@ -3,9 +3,9 @@
3
3
  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
- # be `@imported`. This includes path is used by every instance of
7
- # {Sass::Embedded::Compiler}. They are lower-precedence than any includes
8
- # paths passed in via the `:includes_paths` option.
6
+ # be `@imported`. This include path is used by every instance of
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,25 +13,30 @@ 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
- @includes_paths ||= if ENV['SASS_PATH']
20
- ENV['SASS_PATH'].split(File::PATH_SEPARATOR)
21
- else
22
- []
23
- end
19
+ @include_paths ||= if ENV['SASS_PATH']
20
+ ENV['SASS_PATH'].split(File::PATH_SEPARATOR)
21
+ else
22
+ []
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