sass-embedded 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7d0da205f0cb44177b737a82ce4addac1c94afe4da4a43327963d11feda1fe75
4
+ data.tar.gz: 9821f8807b4eb6b0c612391628d341d416f96df3d3344eeaa762d88d8bbb9f6d
5
+ SHA512:
6
+ metadata.gz: ef2437a502053cdae299f97e9e96b28402d9cdb07c8c40454f9666ccdbe66fc7aa20c17fe629978021e3c4a86d91512c112e04b85a144604fd7ba79992081663
7
+ data.tar.gz: af805efa9a30ca83af3e152cb30b0a72649071e9fd2d2f08ea968a4eedab170c02367c2a5afed84156b4c29e71294e8b9be3bb79371ef8388436fe71adb8ea2e
@@ -0,0 +1,47 @@
1
+ name: build
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ build:
11
+
12
+ runs-on: ${{ matrix.os }}
13
+
14
+ strategy:
15
+ matrix:
16
+ os: [macos-latest, ubuntu-latest, windows-latest]
17
+ ruby-version: ['2.6', '2.7', '3.0']
18
+
19
+ steps:
20
+ - name: Checkout
21
+ uses: actions/checkout@v2
22
+
23
+ - name: Set up Ruby
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby-version }}
27
+ bundler-cache: true
28
+
29
+ - name: Download dart-sass-embedded
30
+ run: bundle exec rake download
31
+ env:
32
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33
+
34
+ - name: Test
35
+ run: bundle exec rake
36
+
37
+ - name: Build Gem
38
+ run: rake build
39
+
40
+ - name: Install Gem
41
+ run: rake install
42
+ env:
43
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44
+
45
+ - name: Test Gem
46
+ run: |-
47
+ ruby -r sass -e "puts Sass.render({ data: 'h1 { color: black; }' })[:css]"
data/.gitignore ADDED
@@ -0,0 +1,45 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Documentation cache and generated files:
28
+ /.yardoc/
29
+ /_yardoc/
30
+ /doc/
31
+ /rdoc/
32
+
33
+ ## Environment normalization:
34
+ /.bundle/
35
+ /vendor/bundle
36
+ /lib/bundler/man/
37
+
38
+ # for a library or gem, you might want to ignore these files since the code is
39
+ # intended to run in multiple environments; otherwise, check them in:
40
+ /Gemfile.lock
41
+ /.ruby-version
42
+ /.ruby-gemset
43
+
44
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
45
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2021, なつき
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Embedded Sass Host for Ruby
2
+
3
+ This is a Ruby library that implements the host side of the [Embedded Sass protocol](https://github.com/sass/sass-embedded-protocol).
4
+
5
+ It exposes a Ruby API for Sass that's backed by a native [Dart Sass](https://sass-lang.com/dart-sass) executable.
6
+
7
+ ## Usage
8
+
9
+ ``` ruby
10
+ require "sass"
11
+
12
+ Sass.render({
13
+ file: "style.scss"
14
+ })
15
+ ```
16
+
17
+ ---
18
+
19
+ Disclaimer: this is not an official Google product.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ task default: :test
4
+
5
+ desc 'Download dart-sass-embedded'
6
+ task :download do
7
+ require_relative 'ext/sass_embedded/extconf'
8
+ end
9
+
10
+ desc 'Run all tests'
11
+ task :test do
12
+ $LOAD_PATH.unshift('lib', 'test')
13
+ Dir.glob('./test/**/*_test.rb') { |f| require f }
14
+ end
@@ -0,0 +1,2 @@
1
+ /sass_embedded*
2
+ /embedded_sass_pb.*
@@ -0,0 +1,23 @@
1
+ .PHONY: install
2
+ install:
3
+ ifeq ($(OS),Windows_NT)
4
+ powershell -command "Get-Item *.zip | Expand-Archive -DestinationPath (Get-Location) -Force"
5
+ else
6
+ tar -vxzf *.tar.gz
7
+ endif
8
+
9
+ .PHONY: clean
10
+ clean:
11
+ ifeq ($(OS),Windows_NT)
12
+ powershell -command "Remove-Item sass_embedded -Recurse -Force -ErrorAction Ignore"
13
+ else
14
+ rm -rf sass_embedded
15
+ endif
16
+
17
+ .PHONY: distclean
18
+ distclean: clean
19
+ ifeq ($(OS),Windows_NT)
20
+ powershell -command "Remove-Item sass_embedded-*.zip -Force -ErrorAction Ignore"
21
+ else
22
+ rm -rf sass_embedded-*.tar.gz
23
+ endif
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "mkmf"
4
+ require "json"
5
+ require "open-uri"
6
+ require_relative "../../lib/sass/platform"
7
+
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
15
+
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
20
+ end
21
+ end
22
+ end
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"
55
+ end
56
+
57
+ url = "https://github.com/#{repo}/releases/download/#{release}/sass_embedded-#{release}-#{os}-#{arch}.#{ext}"
58
+
59
+ begin
60
+ download url
61
+ rescue
62
+ raise "Failed to download: #{url}"
63
+ end
64
+ end
65
+
66
+ system("make", "-C", __dir__, "distclean")
67
+ download_sass_embedded
68
+ system("make", "-C", __dir__, "install")
69
+
70
+ File.open(File.absolute_path("sass_embedded.#{RbConfig::CONFIG['DLEXT']}", __dir__), "w") {}
71
+
72
+ $makefile_created = true
data/lib/sass.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # The global include_paths for Sass files. This is meant for plugins and
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.
9
+ #
10
+ # If the `SASS_PATH` environment variable is set,
11
+ # the initial value of `include_paths` will be initialized based on that.
12
+ # The variable should be a colon-separated list of path names
13
+ # (semicolon-separated on Windows).
14
+ #
15
+ # @example
16
+ # Sass.include_paths << File.dirname(__FILE__ + '/sass')
17
+ # @return [Array<String, Pathname>]
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
24
+ end
25
+
26
+ def self.render options
27
+ @compiler ||= Sass::Embedded::Compiler.new
28
+ @compiler.render options
29
+ end
30
+ end
31
+
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"
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ module Embedded
5
+ class Compiler
6
+
7
+ def initialize
8
+ if defined? @@pwd
9
+ if @@pwd == Dir.pwd
10
+ return
11
+ else
12
+ @@transport.close
13
+ end
14
+ end
15
+
16
+ @@transport = Transport.new
17
+ @@pwd = Dir.pwd
18
+
19
+ @@id_semaphore = Mutex.new
20
+ @@id = 0
21
+ end
22
+
23
+ def render options
24
+ start = Sass::Util.now
25
+
26
+ if options[:file].nil? && options[:data].nil?
27
+ raise Sass::NotRenderedError.new 'Either :data or :file must be set.'
28
+ end
29
+
30
+ if options[:file].nil? && Dir.pwd != @@pwd
31
+ raise Sass::NotRenderedError.new 'Working directory changed after launching `dart-sass-embedded`.'
32
+ end
33
+
34
+ string = options[:data] ? Sass::EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
35
+ :source => options[:data],
36
+ :url => options[:file] ? Sass::Util.file_uri(options[:file]) : 'stdin',
37
+ :syntax => options[:indented_syntax] == true ? Sass::EmbeddedProtocol::Syntax::INDENTED : Sass::EmbeddedProtocol::Syntax::SCSS
38
+ ) : nil
39
+
40
+ path = options[:data] ? nil : options[:file]
41
+
42
+ style = case options[:output_style]&.to_sym
43
+ when :expanded, nil
44
+ Sass::EmbeddedProtocol::OutputStyle::EXPANDED
45
+ when :compressed
46
+ Sass::EmbeddedProtocol::OutputStyle::COMPRESSED
47
+ when :nested, :compact
48
+ raise Sass::UnsupportedValue.new "#{options[:output_style]} is not a supported :output_style"
49
+ else
50
+ raise Sass::InvalidStyleError.new "#{options[:output_style]} is not a valid :output_style"
51
+ end
52
+
53
+ source_map = options[:source_map].is_a? String || (options[:source_map] == true && !!options[:out_file])
54
+
55
+ # 1. Loading a file relative to the file in which the @use or @import appeared.
56
+ # 2. Each custom importer.
57
+ # 3. Loading a file relative to the current working directory.
58
+ # 4. Each load path in includePaths
59
+ # 5. Each load path specified in the SASS_PATH environment variable, which should be semicolon-separated on Windows and colon-separated elsewhere.
60
+ importers = (options[:importer] ? [
61
+ Sass::EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new( :importer_id => 0 )
62
+ ] : []).concat(
63
+ Sass.include_paths.concat(options[:include_paths] || [])
64
+ .map { |path| Sass::EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
65
+ :path => File.absolute_path(path)
66
+ )}
67
+ )
68
+
69
+ signatures = []
70
+ functions = {}
71
+ options[:functions]&.each { |signature, function|
72
+ signatures.push signature
73
+ functions[signature.to_s.split('(')[0].chomp] = function
74
+ }
75
+
76
+ compilation_id = next_id
77
+
78
+ compile_request = Sass::EmbeddedProtocol::InboundMessage::CompileRequest.new(
79
+ :id => compilation_id,
80
+ :string => string,
81
+ :path => path,
82
+ :style => style,
83
+ :source_map => source_map,
84
+ :importers => importers,
85
+ :global_functions => options[:functions] ? signatures : [],
86
+ :alert_color => true,
87
+ :alert_ascii => true
88
+ )
89
+
90
+ response = @@transport.send compile_request, compilation_id
91
+
92
+ file = options[:file] || 'stdin'
93
+ canonicalizations = {}
94
+ imports = {}
95
+
96
+ loop do
97
+ case response
98
+ when Sass::EmbeddedProtocol::OutboundMessage::CompileResponse
99
+ break
100
+ when Sass::EmbeddedProtocol::OutboundMessage::CanonicalizeRequest
101
+ url = Sass::Util.file_uri(File.absolute_path(response.url, File.dirname(file)))
102
+
103
+ if canonicalizations.has_key? url
104
+ canonicalizations[url].id = response.id
105
+ else
106
+ resolved = nil
107
+ options[:importer].each { |importer|
108
+ begin
109
+ resolved = importer.call response.url, file
110
+ rescue Exception => error
111
+ resolved = error
112
+ end
113
+ break if resolved
114
+ }
115
+ if resolved.nil?
116
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
117
+ :id => response.id,
118
+ :url => url
119
+ )
120
+ elsif resolved.is_a? Exception
121
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
122
+ :id => response.id,
123
+ :error => resolved.message
124
+ )
125
+ elsif resolved.has_key? :contents
126
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
127
+ :id => response.id,
128
+ :url => url
129
+ )
130
+ imports[url] = Sass::EmbeddedProtocol::InboundMessage::ImportResponse.new(
131
+ :id => response.id,
132
+ :success => Sass::EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
133
+ :contents => resolved[:contents],
134
+ :syntax => Sass::EmbeddedProtocol::Syntax::SCSS,
135
+ :source_map_url => nil
136
+ )
137
+ )
138
+ elsif resolved.has_key? :file
139
+ canonicalized_url = Sass::Util.file_uri(resolved[:file])
140
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
141
+ :id => response.id,
142
+ :url => canonicalized_url
143
+ )
144
+ imports[canonicalized_url] = Sass::EmbeddedProtocol::InboundMessage::ImportResponse.new(
145
+ :id => response.id,
146
+ :success => Sass::EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
147
+ :contents => File.read(resolved[:file]),
148
+ :syntax => Sass::EmbeddedProtocol::Syntax::SCSS,
149
+ :source_map_url => nil
150
+ )
151
+ )
152
+ else
153
+ canonicalizations[url] = Sass::EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
154
+ :id => response.id,
155
+ :error => "Unexpected value returned from importer: #{resolved}"
156
+ )
157
+ end
158
+ end
159
+
160
+ response = @@transport.send canonicalizations[url], compilation_id
161
+ when Sass::EmbeddedProtocol::OutboundMessage::ImportRequest
162
+ url = response.url
163
+
164
+ if imports.has_key? url
165
+ imports[url].id = response.id
166
+ else
167
+ imports[url] = Sass::EmbeddedProtocol::InboundMessage::ImportResponse.new(
168
+ :id => response.id,
169
+ :error => "Failed to import: #{url}"
170
+ )
171
+ end
172
+
173
+ response = @@transport.send imports[url], compilation_id
174
+ when Sass::EmbeddedProtocol::OutboundMessage::FunctionCallRequest
175
+ begin
176
+ message = Sass::EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
177
+ :id => response.id,
178
+ :success => functions[response.name].call(*response.arguments)
179
+ )
180
+ rescue Exception => error
181
+ message = Sass::EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
182
+ :id => response.id,
183
+ :error => error.message
184
+ )
185
+ end
186
+
187
+ response = @@transport.send message, compilation_id
188
+ when Sass::EmbeddedProtocol::ProtocolError
189
+ raise Sass::ProtocolError.new response.message
190
+ else
191
+ raise Sass::ProtocolError.new "Unexpected packet received: #{response}"
192
+ end
193
+ end
194
+
195
+ if response.failure
196
+ raise Sass::CompilationError.new(
197
+ response.failure.message,
198
+ response.failure.formatted,
199
+ response.failure.span ? response.failure.span.url : nil,
200
+ response.failure.span ? response.failure.span.start.line + 1 : nil,
201
+ response.failure.span ? response.failure.span.start.column + 1 : nil,
202
+ 1
203
+ )
204
+ end
205
+
206
+ finish = Sass::Util.now
207
+
208
+ return {
209
+ css: response.success.css,
210
+ map: response.success.source_map,
211
+ stats: {
212
+ entry: options[:file] || 'data',
213
+ start: start,
214
+ end: finish,
215
+ duration: finish - start
216
+ }
217
+ }
218
+ end
219
+
220
+ private
221
+
222
+ def info
223
+ version_response = @@transport.send Sass::EmbeddedProtocol::InboundMessage::VersionRequest.new(
224
+ :id => next_id
225
+ )
226
+ return {
227
+ compiler_version: version_response.compiler_version,
228
+ protocol_version: version_response.protocol_version,
229
+ implementation_name: version_response.implementation_name,
230
+ implementation_version: version_response.implementation_version
231
+ }
232
+ end
233
+
234
+ def next_id
235
+ @@id_semaphore.synchronize {
236
+ @@id += 1
237
+ if @@id == Transport::PROTOCOL_ERROR_ID
238
+ @@id = 0
239
+ end
240
+ @@id
241
+ }
242
+ end
243
+
244
+ def restart
245
+ @@transport.close
246
+ @@transport = Transport.new
247
+ end
248
+ end
249
+ end
250
+ end