sass-embedded 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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