sass-embedded 0.2.1 → 0.4.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 +4 -4
- data/.github/workflows/build.yml +2 -2
- data/.rubocop.yml +14 -0
- data/README.md +3 -3
- data/Rakefile +11 -4
- data/ext/{sass_embedded/.gitignore → .gitignore} +0 -0
- data/ext/Makefile +51 -0
- data/ext/extconf.rb +160 -0
- data/lib/sass.rb +11 -3
- data/lib/sass/embedded.rb +374 -174
- data/lib/sass/error.rb +4 -9
- data/lib/sass/transport.rb +76 -64
- data/lib/sass/util.rb +22 -3
- data/lib/sass/version.rb +1 -1
- data/sass-embedded.gemspec +4 -2
- data/test/concurrency_test.rb +34 -0
- data/test/functions_test.rb +149 -157
- data/test/{custom_importer_test.rb → importer_test.rb} +24 -31
- data/test/include_paths_test.rb +81 -0
- data/test/indented_syntax_test.rb +61 -0
- data/test/input_test.rb +56 -0
- data/test/output_test.rb +136 -0
- data/test/render_error_test.rb +44 -0
- data/test/render_test.rb +19 -0
- data/test/source_maps_test.rb +178 -0
- data/test/test_helper.rb +1 -0
- metadata +54 -17
- data/ext/sass_embedded/Makefile +0 -35
- data/ext/sass_embedded/extconf.rb +0 -124
- data/test/compiler_test.rb +0 -276
- data/test/error_test.rb +0 -31
- data/test/output_style_test.rb +0 -93
- data/test/sass_test.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9afb73da6288ecf0360685cc9ddb4c072eb8840da50acaa8a52b67f09998e6c
|
4
|
+
data.tar.gz: 1a20d885e2e9f55ffd861f9428499f4c3bdea64a4aea81e44f356af8be69eccc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 679cfc5b502de2fdc5b33c8117b7e1d68f94acd62c7afbe04ebb17f9bceacdc677e8b35197cf239f92fed4e3d523468d71798d899c360d8402be127bdb50ac48
|
7
|
+
data.tar.gz: e1d698b3980914299ee2ce570bd1f109a875859c147f8930366df8e2dabcae94f0e0795339f6614f3f7828e365c191ba24554e30869e4bd106f565f1cbbb9af2
|
data/.github/workflows/build.yml
CHANGED
@@ -27,7 +27,7 @@ jobs:
|
|
27
27
|
bundler-cache: true
|
28
28
|
|
29
29
|
- name: Download dart-sass-embedded
|
30
|
-
run: bundle exec rake
|
30
|
+
run: bundle exec rake extconf
|
31
31
|
env:
|
32
32
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
33
33
|
|
@@ -41,4 +41,4 @@ jobs:
|
|
41
41
|
|
42
42
|
- name: Test Gem
|
43
43
|
run: |-
|
44
|
-
ruby -r sass -e "puts Sass.render(
|
44
|
+
ruby -r sass -e "puts Sass.render(data: 'h1 { color: black; }')[:css]"
|
data/.rubocop.yml
ADDED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Embedded Sass Host for Ruby
|
2
2
|
|
3
|
+
[](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.
|
@@ -9,9 +11,7 @@ It exposes a Ruby API for Sass that's backed by a native [Dart Sass](https://sas
|
|
9
11
|
``` ruby
|
10
12
|
require "sass"
|
11
13
|
|
12
|
-
Sass.render(
|
13
|
-
file: "style.scss"
|
14
|
-
})
|
14
|
+
Sass.render(file: "style.scss")
|
15
15
|
```
|
16
16
|
|
17
17
|
---
|
data/Rakefile
CHANGED
@@ -2,15 +2,22 @@
|
|
2
2
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
4
|
|
5
|
-
task default:
|
5
|
+
task default: %i[rubocop test]
|
6
6
|
|
7
7
|
desc 'Download dart-sass-embedded'
|
8
|
-
task :
|
9
|
-
|
8
|
+
task :extconf do
|
9
|
+
system('make', '-C', 'ext')
|
10
10
|
end
|
11
11
|
|
12
12
|
desc 'Run all tests'
|
13
|
-
task :
|
13
|
+
task test: :extconf do
|
14
14
|
$LOAD_PATH.unshift('lib', 'test')
|
15
15
|
Dir.glob('./test/**/*_test.rb').sort.each { |f| require f }
|
16
16
|
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'rubocop/rake_task'
|
20
|
+
RuboCop::RakeTask.new
|
21
|
+
rescue LoadError
|
22
|
+
nil
|
23
|
+
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,160 @@
|
|
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
|
+
# Install dependencies for sass-embedded during gem install
|
12
|
+
class Extconf
|
13
|
+
def initialize
|
14
|
+
get_with_config('sass-embedded', true) { latest_sass_embedded }
|
15
|
+
get_with_config('sass-embedded-protocol', true) { latest_sass_embedded_protocol }
|
16
|
+
get_with_config('protoc', true) { latest_protoc }
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def get_with_config(config, default)
|
22
|
+
val = with_config(config, default)
|
23
|
+
case val
|
24
|
+
when true
|
25
|
+
if block_given?
|
26
|
+
get yield
|
27
|
+
else
|
28
|
+
get default
|
29
|
+
end
|
30
|
+
when false
|
31
|
+
nil
|
32
|
+
else
|
33
|
+
get val
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def get(uri_s)
|
38
|
+
uri = URI.parse(uri_s)
|
39
|
+
path = File.absolute_path(File.basename(uri.path), __dir__)
|
40
|
+
if uri.is_a?(URI::File) || uri.instance_of?(URI::Generic)
|
41
|
+
FileUtils.copy_file uri.path, path
|
42
|
+
elsif uri.respond_to? :open
|
43
|
+
uri.open do |source|
|
44
|
+
File.open(path, 'wb') do |destination|
|
45
|
+
destination.write source.read
|
46
|
+
end
|
47
|
+
end
|
48
|
+
else
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
rescue StandardError
|
52
|
+
raise "Failed to get: #{uri}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def latest_release(repo, prerelease: false)
|
56
|
+
if prerelease
|
57
|
+
headers = {}
|
58
|
+
headers['Authorization'] = "token #{ENV['GITHUB_TOKEN']}" if ENV['GITHUB_TOKEN']
|
59
|
+
URI.parse("https://api.github.com/repos/#{repo}/releases").open(headers) do |file|
|
60
|
+
JSON.parse(file.read)[0]['tag_name']
|
61
|
+
end
|
62
|
+
else
|
63
|
+
URI.parse("https://github.com/#{repo}/releases/latest").open do |file|
|
64
|
+
File.basename file.base_uri.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def latest_sass_embedded
|
70
|
+
repo = 'sass/dart-sass-embedded'
|
71
|
+
|
72
|
+
# TODO, don't use prerelease once a release is available
|
73
|
+
tag_name = latest_release repo, prerelease: true
|
74
|
+
|
75
|
+
os = case Sass::Platform::OS
|
76
|
+
when 'darwin'
|
77
|
+
'macos'
|
78
|
+
when 'linux'
|
79
|
+
'linux'
|
80
|
+
when 'windows'
|
81
|
+
'windows'
|
82
|
+
else
|
83
|
+
raise "Unsupported OS: #{Sass::Platform::OS}"
|
84
|
+
end
|
85
|
+
|
86
|
+
arch = case Sass::Platform::ARCH
|
87
|
+
when 'x86_64'
|
88
|
+
'x64'
|
89
|
+
when 'i386'
|
90
|
+
'ia32'
|
91
|
+
else
|
92
|
+
raise "Unsupported Arch: #{Sass::Platform::ARCH}"
|
93
|
+
end
|
94
|
+
|
95
|
+
ext = case os
|
96
|
+
when 'windows'
|
97
|
+
'zip'
|
98
|
+
else
|
99
|
+
'tar.gz'
|
100
|
+
end
|
101
|
+
|
102
|
+
"https://github.com/#{repo}/releases/download/#{tag_name}/sass_embedded-#{tag_name}-#{os}-#{arch}.#{ext}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def latest_protoc
|
106
|
+
repo = 'protocolbuffers/protobuf'
|
107
|
+
|
108
|
+
tag_name = latest_release repo
|
109
|
+
|
110
|
+
os = case Sass::Platform::OS
|
111
|
+
when 'darwin'
|
112
|
+
'osx'
|
113
|
+
when 'linux'
|
114
|
+
'linux'
|
115
|
+
when 'windows'
|
116
|
+
'win'
|
117
|
+
else
|
118
|
+
raise "Unsupported OS: #{Sass::Platform::OS}"
|
119
|
+
end
|
120
|
+
|
121
|
+
arch = case Sass::Platform::ARCH
|
122
|
+
when 'aarch64'
|
123
|
+
'aarch_64'
|
124
|
+
when 'sparcv9'
|
125
|
+
's390'
|
126
|
+
when 'i386'
|
127
|
+
'x86_32'
|
128
|
+
when 'x86_64'
|
129
|
+
'x86_64'
|
130
|
+
when 'powerpc64'
|
131
|
+
'ppcle_64'
|
132
|
+
else
|
133
|
+
raise "Unsupported Arch: #{Sass::Platform::ARCH}"
|
134
|
+
end
|
135
|
+
|
136
|
+
os_arch = case os
|
137
|
+
when 'win'
|
138
|
+
os + arch.split('_').last
|
139
|
+
else
|
140
|
+
"#{os}-#{arch}"
|
141
|
+
end
|
142
|
+
|
143
|
+
ext = 'zip'
|
144
|
+
|
145
|
+
"https://github.com/#{repo}/releases/download/#{tag_name}/protoc-#{tag_name[1..]}-#{os_arch}.#{ext}"
|
146
|
+
end
|
147
|
+
|
148
|
+
def latest_sass_embedded_protocol
|
149
|
+
repo = 'sass/embedded-protocol'
|
150
|
+
|
151
|
+
# TODO: use latest release once available
|
152
|
+
# tag_name = latest_release repo
|
153
|
+
tag_name = 'HEAD'
|
154
|
+
|
155
|
+
"https://raw.githubusercontent.com/#{repo}/#{tag_name}/embedded_sass.proto"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
Sass::Extconf.new
|
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
|
@@ -13,7 +14,7 @@ module Sass
|
|
13
14
|
# (semicolon-separated on Windows).
|
14
15
|
#
|
15
16
|
# @example
|
16
|
-
# Sass.include_paths << File.dirname(__FILE__ + '/sass'
|
17
|
+
# Sass.include_paths << File.dirname(__FILE__) + '/sass'
|
17
18
|
# @return [Array<String, Pathname>]
|
18
19
|
def self.include_paths
|
19
20
|
@include_paths ||= if ENV['SASS_PATH']
|
@@ -23,14 +24,21 @@ module Sass
|
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
|
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
|
41
|
+
@embedded.render(**kwargs)
|
34
42
|
end
|
35
43
|
end
|
36
44
|
|
data/lib/sass/embedded.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
require 'base64'
|
5
|
+
|
3
6
|
module Sass
|
7
|
+
# The interface for using dart-sass-embedded
|
4
8
|
class Embedded
|
5
9
|
def initialize
|
6
10
|
@transport = Transport.new
|
@@ -8,198 +12,107 @@ module Sass
|
|
8
12
|
@id = 0
|
9
13
|
end
|
10
14
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
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]
|
15
|
+
def info
|
16
|
+
@transport.send EmbeddedProtocol::InboundMessage::VersionRequest.new(
|
17
|
+
id: next_id
|
18
|
+
)
|
19
|
+
end
|
25
20
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
21
|
+
def render(data: nil,
|
22
|
+
file: nil,
|
23
|
+
indented_syntax: false,
|
24
|
+
include_paths: [],
|
25
|
+
output_style: :expanded,
|
26
|
+
# precision: 5,
|
27
|
+
indent_type: :space,
|
28
|
+
indent_width: 2,
|
29
|
+
linefeed: :lf,
|
30
|
+
# source_comments: false,
|
31
|
+
source_map: false,
|
32
|
+
out_file: nil,
|
33
|
+
omit_source_map_url: false,
|
34
|
+
# source_map_contents: false,
|
35
|
+
source_map_embed: false,
|
36
|
+
source_map_root: '',
|
37
|
+
functions: {},
|
38
|
+
importer: [])
|
39
|
+
start = Util.now
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
41
|
+
indent_type = parse_indent_type(indent_type)
|
42
|
+
indent_width = parse_indent_width(indent_width)
|
43
|
+
linefeed = parse_linefeed(linefeed)
|
65
44
|
|
66
45
|
compilation_id = next_id
|
67
46
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
47
|
+
renderer = Renderer.new(
|
48
|
+
data: data,
|
49
|
+
file: file,
|
50
|
+
indented_syntax: indented_syntax,
|
51
|
+
include_paths: include_paths,
|
52
|
+
output_style: output_style,
|
73
53
|
source_map: source_map,
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
alert_ascii: true
|
54
|
+
out_file: out_file,
|
55
|
+
functions: functions,
|
56
|
+
importer: importer
|
78
57
|
)
|
79
58
|
|
80
|
-
response = @transport.send compile_request, compilation_id
|
81
|
-
|
82
|
-
file = options[:file] || 'stdin'
|
83
|
-
canonicalizations = {}
|
84
|
-
imports = {}
|
59
|
+
response = @transport.send renderer.compile_request(compilation_id), compilation_id
|
85
60
|
|
86
61
|
loop do
|
87
62
|
case response
|
88
|
-
when
|
63
|
+
when EmbeddedProtocol::OutboundMessage::CompileResponse
|
89
64
|
break
|
90
|
-
when
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
65
|
+
when EmbeddedProtocol::OutboundMessage::CanonicalizeRequest
|
66
|
+
response = @transport.send renderer.canonicalize_response(response), compilation_id
|
67
|
+
when EmbeddedProtocol::OutboundMessage::ImportRequest
|
68
|
+
response = @transport.send renderer.import_response(response), compilation_id
|
69
|
+
when EmbeddedProtocol::OutboundMessage::FunctionCallRequest
|
70
|
+
response = @transport.send renderer.function_call_response(response), compilation_id
|
71
|
+
when EmbeddedProtocol::ProtocolError
|
72
|
+
raise ProtocolError, response.message
|
180
73
|
else
|
181
|
-
raise
|
74
|
+
raise ProtocolError, "Unexpected packet received: #{response}"
|
182
75
|
end
|
183
76
|
end
|
184
77
|
|
185
78
|
if response.failure
|
186
|
-
raise
|
79
|
+
raise RenderError.new(
|
187
80
|
response.failure.message,
|
188
81
|
response.failure.formatted,
|
189
|
-
|
82
|
+
if response.failure.span&.url == ''
|
83
|
+
'stdin'
|
84
|
+
else
|
85
|
+
Util.path(response.failure.span.url)
|
86
|
+
end,
|
190
87
|
response.failure.span ? response.failure.span.start.line + 1 : nil,
|
191
88
|
response.failure.span ? response.failure.span.start.column + 1 : nil,
|
192
89
|
1
|
193
90
|
)
|
194
91
|
end
|
195
92
|
|
196
|
-
|
93
|
+
map, source_map = post_process_map(map: response.success.source_map,
|
94
|
+
file: file,
|
95
|
+
out_file: out_file,
|
96
|
+
source_map: source_map,
|
97
|
+
source_map_root: source_map_root)
|
98
|
+
|
99
|
+
css = post_process_css(css: response.success.css,
|
100
|
+
indent_type: indent_type,
|
101
|
+
indent_width: indent_width,
|
102
|
+
linefeed: linefeed,
|
103
|
+
map: map,
|
104
|
+
out_file: out_file,
|
105
|
+
omit_source_map_url: omit_source_map_url,
|
106
|
+
source_map: source_map,
|
107
|
+
source_map_embed: source_map_embed)
|
108
|
+
|
109
|
+
finish = Util.now
|
197
110
|
|
198
111
|
{
|
199
|
-
css:
|
200
|
-
map:
|
112
|
+
css: css,
|
113
|
+
map: map,
|
201
114
|
stats: {
|
202
|
-
entry:
|
115
|
+
entry: file.nil? ? 'data' : file,
|
203
116
|
start: start,
|
204
117
|
end: finish,
|
205
118
|
duration: finish - start
|
@@ -209,20 +122,113 @@ module Sass
|
|
209
122
|
|
210
123
|
def close
|
211
124
|
@transport.close
|
125
|
+
nil
|
212
126
|
end
|
213
127
|
|
214
128
|
private
|
215
129
|
|
216
|
-
def
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
130
|
+
def post_process_map(map:,
|
131
|
+
file:,
|
132
|
+
out_file:,
|
133
|
+
source_map:,
|
134
|
+
source_map_root:)
|
135
|
+
return if map.nil? || map.empty?
|
136
|
+
|
137
|
+
map_data = JSON.parse(map)
|
138
|
+
|
139
|
+
map_data['sourceRoot'] = source_map_root
|
140
|
+
|
141
|
+
source_map_path = if source_map.is_a? String
|
142
|
+
source_map
|
143
|
+
else
|
144
|
+
"#{out_file}.map"
|
145
|
+
end
|
146
|
+
|
147
|
+
source_map_dir = File.dirname(source_map_path)
|
148
|
+
|
149
|
+
if out_file
|
150
|
+
map_data['file'] = Util.relative(source_map_dir, out_file)
|
151
|
+
elsif file
|
152
|
+
ext = File.extname(file)
|
153
|
+
map_data['file'] = "#{file[0..(ext.empty? ? -1 : -ext.length - 1)]}.css"
|
154
|
+
else
|
155
|
+
map_data['file'] = 'stdin.css'
|
156
|
+
end
|
157
|
+
|
158
|
+
map_data['sources'].map! do |source|
|
159
|
+
if source.start_with? Util::FILE_PROTOCOL
|
160
|
+
Util.relative(source_map_dir, Util.path(source))
|
161
|
+
else
|
162
|
+
source
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
[-JSON.generate(map_data), source_map_path]
|
167
|
+
end
|
168
|
+
|
169
|
+
def post_process_css(css:,
|
170
|
+
indent_type:,
|
171
|
+
indent_width:,
|
172
|
+
linefeed:,
|
173
|
+
map:,
|
174
|
+
omit_source_map_url:,
|
175
|
+
out_file:,
|
176
|
+
source_map:,
|
177
|
+
source_map_embed:)
|
178
|
+
css = +css
|
179
|
+
if indent_width != 2 || indent_type.to_sym != :space
|
180
|
+
indent = indent_type * indent_width
|
181
|
+
css.gsub!(/^ +/) do |space|
|
182
|
+
indent * (space.length / 2)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
css.gsub!("\n", linefeed) if linefeed != "\n"
|
186
|
+
|
187
|
+
unless map.nil? || omit_source_map_url == true
|
188
|
+
url = if source_map_embed
|
189
|
+
"data:application/json;base64,#{Base64.strict_encode64(map)}"
|
190
|
+
elsif out_file
|
191
|
+
Util.relative(File.dirname(out_file), source_map)
|
192
|
+
else
|
193
|
+
source_map
|
194
|
+
end
|
195
|
+
css += "#{linefeed}/*# sourceMappingURL=#{url} */"
|
196
|
+
end
|
197
|
+
|
198
|
+
-css
|
199
|
+
end
|
200
|
+
|
201
|
+
def parse_indent_type(indent_type)
|
202
|
+
case indent_type.to_sym
|
203
|
+
when :space
|
204
|
+
' '
|
205
|
+
when :tab
|
206
|
+
"\t"
|
207
|
+
else
|
208
|
+
raise ArgumentError, 'indent_type must be :space or :tab'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def parse_indent_width(indent_width)
|
213
|
+
raise ArgumentError, 'indent_width must be an integer' unless indent_width.is_a? Integer
|
214
|
+
raise RangeError, 'indent_width must be in between 0 and 10 (inclusive)' unless indent_width.between? 0, 10
|
215
|
+
|
216
|
+
indent_width
|
217
|
+
end
|
218
|
+
|
219
|
+
def parse_linefeed(linefeed)
|
220
|
+
case linefeed.to_sym
|
221
|
+
when :lf
|
222
|
+
"\n"
|
223
|
+
when :lfcr
|
224
|
+
"\n\r"
|
225
|
+
when :cr
|
226
|
+
"\r"
|
227
|
+
when :crlf
|
228
|
+
"\r\n"
|
229
|
+
else
|
230
|
+
raise ArgumentError, 'linefeed must be one of :lf, :lfcr, :cr, :crlf'
|
231
|
+
end
|
226
232
|
end
|
227
233
|
|
228
234
|
def next_id
|
@@ -232,5 +238,199 @@ module Sass
|
|
232
238
|
@id
|
233
239
|
end
|
234
240
|
end
|
241
|
+
|
242
|
+
# Helper class that maintains render state
|
243
|
+
class Renderer
|
244
|
+
def initialize(data:,
|
245
|
+
file:,
|
246
|
+
indented_syntax:,
|
247
|
+
include_paths:,
|
248
|
+
output_style:,
|
249
|
+
source_map:,
|
250
|
+
out_file:,
|
251
|
+
functions:,
|
252
|
+
importer:)
|
253
|
+
raise ArgumentError, 'either data or file must be set' if file.nil? && data.nil?
|
254
|
+
|
255
|
+
@data = data
|
256
|
+
@file = file
|
257
|
+
@indented_syntax = indented_syntax
|
258
|
+
@include_paths = include_paths
|
259
|
+
@output_style = output_style
|
260
|
+
@source_map = source_map
|
261
|
+
@out_file = out_file
|
262
|
+
@global_functions = functions.keys
|
263
|
+
@functions = functions.transform_keys do |key|
|
264
|
+
key.to_s.split('(')[0].chomp
|
265
|
+
end
|
266
|
+
@importer = importer
|
267
|
+
@import_responses = {}
|
268
|
+
end
|
269
|
+
|
270
|
+
def compile_request(id)
|
271
|
+
EmbeddedProtocol::InboundMessage::CompileRequest.new(
|
272
|
+
id: id,
|
273
|
+
string: string,
|
274
|
+
path: path,
|
275
|
+
style: style,
|
276
|
+
source_map: source_map,
|
277
|
+
importers: importers,
|
278
|
+
global_functions: global_functions,
|
279
|
+
alert_color: $stderr.tty?,
|
280
|
+
alert_ascii: Platform::OS == 'windows'
|
281
|
+
)
|
282
|
+
end
|
283
|
+
|
284
|
+
def canonicalize_response(canonicalize_request)
|
285
|
+
url = Util.file_uri(File.absolute_path(canonicalize_request.url, (@file.nil? ? 'stdin' : @file)))
|
286
|
+
|
287
|
+
begin
|
288
|
+
result = @importer[canonicalize_request.importer_id].call canonicalize_request.url, @file
|
289
|
+
raise result if result.is_a? StandardError
|
290
|
+
rescue StandardError => e
|
291
|
+
return EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
|
292
|
+
id: canonicalize_request.id,
|
293
|
+
error: e.message
|
294
|
+
)
|
295
|
+
end
|
296
|
+
|
297
|
+
if result&.key? :contents
|
298
|
+
@import_responses[url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
|
299
|
+
id: canonicalize_request.id,
|
300
|
+
success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
|
301
|
+
contents: result[:contents],
|
302
|
+
syntax: EmbeddedProtocol::Syntax::SCSS,
|
303
|
+
source_map_url: nil
|
304
|
+
)
|
305
|
+
)
|
306
|
+
EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
|
307
|
+
id: canonicalize_request.id,
|
308
|
+
url: url
|
309
|
+
)
|
310
|
+
elsif result&.key? :file
|
311
|
+
canonicalized_url = Util.file_uri(result[:file])
|
312
|
+
|
313
|
+
# TODO: FileImportRequest is not supported yet.
|
314
|
+
# Workaround by reading contents and return it when server asks
|
315
|
+
@import_responses[canonicalized_url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
|
316
|
+
id: canonicalize_request.id,
|
317
|
+
success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
|
318
|
+
contents: File.read(result[:file]),
|
319
|
+
syntax: EmbeddedProtocol::Syntax::SCSS,
|
320
|
+
source_map_url: nil
|
321
|
+
)
|
322
|
+
)
|
323
|
+
|
324
|
+
EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
|
325
|
+
id: canonicalize_request.id,
|
326
|
+
url: canonicalized_url
|
327
|
+
)
|
328
|
+
else
|
329
|
+
EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
|
330
|
+
id: canonicalize_request.id
|
331
|
+
)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def import_response(import_request)
|
336
|
+
url = import_request.url
|
337
|
+
|
338
|
+
if @import_responses.key? url
|
339
|
+
@import_responses[url].id = import_request.id
|
340
|
+
else
|
341
|
+
@import_responses[url] = EmbeddedProtocol::InboundMessage::ImportResponse.new(
|
342
|
+
id: import_request.id,
|
343
|
+
error: "Failed to import: #{url}"
|
344
|
+
)
|
345
|
+
end
|
346
|
+
|
347
|
+
@import_responses[url]
|
348
|
+
end
|
349
|
+
|
350
|
+
def function_call_response(function_call_request)
|
351
|
+
EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
|
352
|
+
id: function_call_request.id,
|
353
|
+
success: @functions[function_call_request.name].call(*function_call_request.arguments)
|
354
|
+
)
|
355
|
+
rescue StandardError => e
|
356
|
+
EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
|
357
|
+
id: function_call_request.id,
|
358
|
+
error: e.message
|
359
|
+
)
|
360
|
+
end
|
361
|
+
|
362
|
+
private
|
363
|
+
|
364
|
+
def syntax
|
365
|
+
if @indented_syntax == true
|
366
|
+
EmbeddedProtocol::Syntax::INDENTED
|
367
|
+
else
|
368
|
+
EmbeddedProtocol::Syntax::SCSS
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def url
|
373
|
+
return if @file.nil?
|
374
|
+
|
375
|
+
Util.file_uri @file
|
376
|
+
end
|
377
|
+
|
378
|
+
def string
|
379
|
+
return if @data.nil?
|
380
|
+
|
381
|
+
EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
|
382
|
+
source: @data,
|
383
|
+
url: url,
|
384
|
+
syntax: syntax
|
385
|
+
)
|
386
|
+
end
|
387
|
+
|
388
|
+
def path
|
389
|
+
@file if @data.nil?
|
390
|
+
end
|
391
|
+
|
392
|
+
def style
|
393
|
+
case @output_style&.to_sym
|
394
|
+
when :expanded
|
395
|
+
EmbeddedProtocol::OutputStyle::EXPANDED
|
396
|
+
when :compressed
|
397
|
+
EmbeddedProtocol::OutputStyle::COMPRESSED
|
398
|
+
when :nested, :compact
|
399
|
+
raise ArgumentError, "#{@output_style} is not a supported output_style"
|
400
|
+
else
|
401
|
+
raise ArgumentError, "#{@output_style} is not a valid utput_style"
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def source_map
|
406
|
+
@source_map.is_a?(String) || (@source_map == true && !@out_file.nil?)
|
407
|
+
end
|
408
|
+
|
409
|
+
attr_reader :global_functions
|
410
|
+
|
411
|
+
# Order
|
412
|
+
# 1. Loading a file relative to the file in which the @use or @import appeared.
|
413
|
+
# 2. Each custom importer.
|
414
|
+
# 3. Loading a file relative to the current working directory.
|
415
|
+
# 4. Each load path in includePaths
|
416
|
+
# 5. Each load path specified in the SASS_PATH environment variable, which should be semicolon-separated on Windows and colon-separated elsewhere.
|
417
|
+
def importers
|
418
|
+
custom_importers = @importer.map.with_index do |_, id|
|
419
|
+
EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
|
420
|
+
importer_id: id
|
421
|
+
)
|
422
|
+
end
|
423
|
+
|
424
|
+
include_path_importers = @include_paths
|
425
|
+
.concat(Sass.include_paths)
|
426
|
+
.map do |include_path|
|
427
|
+
EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
|
428
|
+
path: File.absolute_path(include_path)
|
429
|
+
)
|
430
|
+
end
|
431
|
+
|
432
|
+
custom_importers.concat include_path_importers
|
433
|
+
end
|
434
|
+
end
|
235
435
|
end
|
236
436
|
end
|