dartsass-ruby 3.0.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 +7 -0
- data/LICENSE.txt +24 -0
- data/README.md +83 -0
- data/lib/dartsass-ruby.rb +3 -0
- data/lib/sassc/dependency.rb +17 -0
- data/lib/sassc/engine.rb +220 -0
- data/lib/sassc/error.rb +35 -0
- data/lib/sassc/functions_handler.rb +81 -0
- data/lib/sassc/import_handler.rb +222 -0
- data/lib/sassc/importer.rb +31 -0
- data/lib/sassc/protocol.rb +11 -0
- data/lib/sassc/sass_2_scss.rb +12 -0
- data/lib/sassc/script/functions.rb +8 -0
- data/lib/sassc/script/value/bool.rb +32 -0
- data/lib/sassc/script/value/color.rb +95 -0
- data/lib/sassc/script/value/list.rb +136 -0
- data/lib/sassc/script/value/map.rb +69 -0
- data/lib/sassc/script/value/number.rb +389 -0
- data/lib/sassc/script/value/string.rb +96 -0
- data/lib/sassc/script/value.rb +137 -0
- data/lib/sassc/script/value_conversion.rb +117 -0
- data/lib/sassc/script.rb +16 -0
- data/lib/sassc/url.rb +40 -0
- data/lib/sassc/util.rb +15 -0
- data/lib/sassc/version.rb +5 -0
- data/lib/sassc.rb +56 -0
- metadata +185 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a89dda4fd651f7a90dc3bfb03ef4aeebb279f5e92ea2b99c970af0f88e3e5290
|
4
|
+
data.tar.gz: 18acbc815964dc76364673e62fde9349c06a0c81731a74ea2932e68c0c047072
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d0ba7bb44579c5a661894debb768fab28b4a5d9f1febe88a1f4d4a2565863e7956554d36d38b7037eb9329605571e322af6f728254b4ecdea88e880d80a333d0
|
7
|
+
data.tar.gz: d12ae55b2e363cdca014d8b04fb29324ca69b2114951738117b15259d63877e8f11b23b99ea6942e192ae3c451b7b29387c22676adc96a3c2ec7ce012d6c6ce8
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) Ryan Boland & Contributors
|
2
|
+
Copyright (c) Natsuki
|
3
|
+
Copyright (c) TableCheck
|
4
|
+
|
5
|
+
MIT License
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
"Software"), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Dart Sass for Ruby
|
2
|
+
|
3
|
+
[](https://github.com/tablecheck/dartsass-ruby/actions/workflows/build.yml)
|
4
|
+
[](https://rubygems.org/gems/dartsass-ruby)
|
5
|
+
|
6
|
+
Use [dart-sass](https://sass-lang.com/dart-sass) with Ruby and Sprockets.
|
7
|
+
|
8
|
+
This gem is a fork of [sass/sassc-ruby](https://github.com/sass/sassc-ruby)
|
9
|
+
which maintains API compatibility but delegates to the
|
10
|
+
[sass-embedded gem](https://github.com/ntkme/sass-embedded-host-ruby)
|
11
|
+
which provides native binaries for Dart Sass (instead of the libsass
|
12
|
+
C implmentation.)
|
13
|
+
|
14
|
+
For ease of upgrading, the root namespace `::SassC` is still used by this gem,
|
15
|
+
although it is now a misnomer. This is planned to be migrated in a future
|
16
|
+
major version.
|
17
|
+
|
18
|
+
### Upgrading to Dart Sass
|
19
|
+
|
20
|
+
The interface of [sassc-ruby](https://github.com/sass/sassc-ruby) is largely unchanged, however:
|
21
|
+
|
22
|
+
1. Option `style: :nested` and `style: :compact` behave as `style: :expanded`. Use `style: :compressed` for minification.
|
23
|
+
2. Option `:precision` is ignored.
|
24
|
+
3. Option `:line_comments` is ignored.
|
25
|
+
4. `Sass2Scss` functionality has been removed.
|
26
|
+
|
27
|
+
See [the dart-sass documentation](https://github.com/sass/dart-sass#behavioral-differences-from-ruby-sass) for other differences.
|
28
|
+
|
29
|
+
## Installation
|
30
|
+
|
31
|
+
Add this line to your application's Gemfile:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
gem 'dartsass-ruby'
|
35
|
+
```
|
36
|
+
|
37
|
+
Rails/Sprockets users should additionally add [sassc-rails](https://github.com/sass/sassc-rails):
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
gem 'sassc-rails'
|
41
|
+
```
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
This library utilizes [dart-sass](https://github.com/sass/dart-sass) to compile
|
46
|
+
SCSS or SASS syntax to CSS. To compile, use a `SassC::Engine`, e.g.:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
SassC::Engine.new(".klass1, .klass2 { color: :red; }", style: :compressed).render
|
50
|
+
```
|
51
|
+
|
52
|
+
## Alternatives
|
53
|
+
|
54
|
+
* [dartsass-rails](https://github.com/rails/dartsass-rails): Rails organization
|
55
|
+
maintains its own wrapper for Dart Sass. Unlike this gem, dartsass-rails does
|
56
|
+
not support Sprockets.
|
57
|
+
|
58
|
+
## Credits
|
59
|
+
|
60
|
+
* This gem is maintained and used in production by [TableCheck](https://www.tablecheck.com/en/join). (We'd be very glad if the Sass organization could take over maintainership in the future!)
|
61
|
+
* Kudos to [@ntkme](https://github.com/ntkme) for dart-sass support.
|
62
|
+
* Credit to [Ryan Boland](https://ryanboland.com) and the authors of the original sassc-rails gem.
|
63
|
+
* See our [awesome contributors](https://github.com/tablecheck/sassc-ruby/graphs/contributors).
|
64
|
+
|
65
|
+
## Changelog
|
66
|
+
|
67
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
68
|
+
|
69
|
+
## Contributing
|
70
|
+
|
71
|
+
### Project Setup
|
72
|
+
|
73
|
+
1. Clone repo
|
74
|
+
1. Install dependencies - `bundle install`
|
75
|
+
1. Run the tests - `bundle exec rake test`
|
76
|
+
|
77
|
+
### Code Changes
|
78
|
+
|
79
|
+
1. Fork it ([https://github.com/sass/sassc-ruby/fork](https://github.com/sass/sassc-ruby/fork))
|
80
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
81
|
+
3. Commit your changes (`git commit -am 'Add some feature'`) - try to include tests
|
82
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
83
|
+
5. Create a new Pull Request
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SassC
|
4
|
+
class Dependency
|
5
|
+
attr_reader :filename
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(filename)
|
9
|
+
@filename = filename
|
10
|
+
@options = { filename: @filename }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_filenames(filenames)
|
14
|
+
filenames.map { |f| new(f) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/sassc/engine.rb
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "error"
|
4
|
+
|
5
|
+
module SassC
|
6
|
+
class Engine
|
7
|
+
OUTPUT_STYLES = %i[
|
8
|
+
sass_style_nested
|
9
|
+
sass_style_expanded
|
10
|
+
sass_style_compact
|
11
|
+
sass_style_compressed
|
12
|
+
]
|
13
|
+
|
14
|
+
attr_reader :template, :options
|
15
|
+
|
16
|
+
def initialize(template, options = {})
|
17
|
+
@template = template
|
18
|
+
@options = options
|
19
|
+
@functions = options.fetch(:functions, Script::Functions)
|
20
|
+
end
|
21
|
+
|
22
|
+
def render
|
23
|
+
return @template.dup if @template.empty?
|
24
|
+
|
25
|
+
result = ::Sass.compile_string(
|
26
|
+
@template,
|
27
|
+
importer: import_handler.setup(nil),
|
28
|
+
load_paths: load_paths,
|
29
|
+
syntax: syntax,
|
30
|
+
url: file_url,
|
31
|
+
|
32
|
+
charset: @options.fetch(:charset, true),
|
33
|
+
source_map: source_map_embed? || !source_map_file.nil?,
|
34
|
+
source_map_include_sources: source_map_contents?,
|
35
|
+
style: output_style,
|
36
|
+
|
37
|
+
functions: functions_handler.setup(nil, functions: @functions),
|
38
|
+
importers: @options.fetch(:importers, []),
|
39
|
+
|
40
|
+
alert_ascii: @options.fetch(:alert_ascii, false),
|
41
|
+
alert_color: @options.fetch(:alert_color, nil),
|
42
|
+
logger: @options.fetch(:logger, nil),
|
43
|
+
quiet_deps: @options.fetch(:quiet_deps, false),
|
44
|
+
verbose: @options.fetch(:verbose, false)
|
45
|
+
)
|
46
|
+
|
47
|
+
@dependencies = result.loaded_urls
|
48
|
+
.filter { |url| url.start_with?(Protocol::FILE) && url != file_url }
|
49
|
+
.map { |url| URL.file_url_to_path(url) }
|
50
|
+
@source_map = post_process_source_map(result.source_map)
|
51
|
+
|
52
|
+
return post_process_css(result.css) unless quiet?
|
53
|
+
rescue ::Sass::CompileError => e
|
54
|
+
line = e.span&.start&.line
|
55
|
+
line += 1 unless line.nil?
|
56
|
+
url = e.span&.url
|
57
|
+
path = (URL.parse(url).route_from(URL.path_to_file_url("#{Dir.pwd}/")) if url&.start_with?(Protocol::FILE))
|
58
|
+
raise SyntaxError.new(e.full_message, filename: path, line: line)
|
59
|
+
end
|
60
|
+
|
61
|
+
def dependencies
|
62
|
+
raise NotRenderedError unless @dependencies
|
63
|
+
Dependency.from_filenames(@dependencies)
|
64
|
+
end
|
65
|
+
|
66
|
+
def source_map
|
67
|
+
raise NotRenderedError unless @source_map
|
68
|
+
@source_map
|
69
|
+
end
|
70
|
+
|
71
|
+
def filename
|
72
|
+
@options[:filename]
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def quiet?
|
78
|
+
@options[:quiet]
|
79
|
+
end
|
80
|
+
|
81
|
+
def precision
|
82
|
+
@options[:precision]
|
83
|
+
end
|
84
|
+
|
85
|
+
def sass?
|
86
|
+
@options[:syntax] && @options[:syntax].to_sym == :sass
|
87
|
+
end
|
88
|
+
|
89
|
+
def line_comments?
|
90
|
+
@options[:line_comments]
|
91
|
+
end
|
92
|
+
|
93
|
+
def source_map_embed?
|
94
|
+
@options[:source_map_embed]
|
95
|
+
end
|
96
|
+
|
97
|
+
def source_map_contents?
|
98
|
+
@options[:source_map_contents]
|
99
|
+
end
|
100
|
+
|
101
|
+
def omit_source_map_url?
|
102
|
+
@options[:omit_source_map_url]
|
103
|
+
end
|
104
|
+
|
105
|
+
def source_map_file
|
106
|
+
@options[:source_map_file]
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_source_map_path?
|
110
|
+
@options.fetch(:validate_source_map_path, true)
|
111
|
+
end
|
112
|
+
|
113
|
+
def import_handler
|
114
|
+
@import_handler ||= ImportHandler.new(@options)
|
115
|
+
end
|
116
|
+
|
117
|
+
def functions_handler
|
118
|
+
@functions_handler = FunctionsHandler.new(@options)
|
119
|
+
end
|
120
|
+
|
121
|
+
def file_url
|
122
|
+
@file_url ||= URL.path_to_file_url(File.absolute_path(filename || 'stdin'))
|
123
|
+
end
|
124
|
+
|
125
|
+
def output_path
|
126
|
+
@output_path ||= @options.fetch(
|
127
|
+
:output_path,
|
128
|
+
("#{filename.delete_suffix(File.extname(filename))}.css" if filename)
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
def output_url
|
133
|
+
@output_url ||= (URL.path_to_file_url(File.absolute_path(output_path)) if output_path)
|
134
|
+
end
|
135
|
+
|
136
|
+
def source_map_file_url
|
137
|
+
return unless source_map_file
|
138
|
+
@source_map_file_url ||=
|
139
|
+
if validate_source_map_path?
|
140
|
+
URL.path_to_file_url(File.absolute_path(source_map_file))
|
141
|
+
else
|
142
|
+
source_map_file
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def output_style_enum
|
147
|
+
@output_style_enum ||= Native::SassOutputStyle[output_style]
|
148
|
+
end
|
149
|
+
|
150
|
+
def output_style
|
151
|
+
@output_style ||= begin
|
152
|
+
style = @options.fetch(:style, :sass_style_nested).to_s
|
153
|
+
style = "sass_style_#{style}" unless style.include?('sass_style_')
|
154
|
+
raise InvalidStyleError unless OUTPUT_STYLES.include?(style.to_sym)
|
155
|
+
|
156
|
+
style = style.delete_prefix('sass_style_').to_sym
|
157
|
+
case style
|
158
|
+
when :nested, :compact
|
159
|
+
:expanded
|
160
|
+
else
|
161
|
+
style
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def syntax
|
167
|
+
syntax = @options.fetch(:syntax, :scss)
|
168
|
+
syntax = :indented if syntax.to_sym == :sass
|
169
|
+
syntax
|
170
|
+
end
|
171
|
+
|
172
|
+
def load_paths
|
173
|
+
@load_paths ||= if @options[:importer].nil?
|
174
|
+
(@options[:load_paths] || []) + SassC.load_paths
|
175
|
+
else
|
176
|
+
[]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def post_process_source_map(source_map)
|
181
|
+
return unless source_map
|
182
|
+
|
183
|
+
url = URL.parse(source_map_file_url || file_url)
|
184
|
+
data = JSON.parse(source_map)
|
185
|
+
data["file"] = if validate_source_map_path?
|
186
|
+
URL.parse(output_url).route_from(url).to_s
|
187
|
+
else
|
188
|
+
output_url
|
189
|
+
end
|
190
|
+
data["sources"].map! do |source|
|
191
|
+
if source.start_with?(Protocol::FILE) && validate_source_map_path?
|
192
|
+
URL.parse(source).route_from(url).to_s
|
193
|
+
else
|
194
|
+
source
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
JSON.generate(data)
|
199
|
+
end
|
200
|
+
|
201
|
+
def post_process_css(css)
|
202
|
+
css += "\n" unless css.empty?
|
203
|
+
unless @source_map.nil? || omit_source_map_url?
|
204
|
+
url = URL.parse(output_url || file_url)
|
205
|
+
source_mapping_url =
|
206
|
+
if source_map_embed?
|
207
|
+
"data:application/json;base64,#{Base64.strict_encode64(@source_map)}"
|
208
|
+
else
|
209
|
+
if validate_source_map_path?
|
210
|
+
URL.parse(source_map_file_url).route_from(url).to_s
|
211
|
+
else
|
212
|
+
source_map_file_url
|
213
|
+
end
|
214
|
+
end
|
215
|
+
css += "\n/*# sourceMappingURL=#{source_mapping_url} */"
|
216
|
+
end
|
217
|
+
css
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
data/lib/sassc/error.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
module SassC
|
6
|
+
|
7
|
+
class BaseError < StandardError; end
|
8
|
+
class NotRenderedError < BaseError; end
|
9
|
+
class InvalidStyleError < BaseError; end
|
10
|
+
class UnsupportedValue < BaseError; end
|
11
|
+
|
12
|
+
# When dealing with SyntaxErrors,
|
13
|
+
# it's important to provide filename and line number information.
|
14
|
+
# This will be used in various error reports to users, including backtraces.
|
15
|
+
|
16
|
+
class SyntaxError < BaseError
|
17
|
+
|
18
|
+
def initialize(message, filename: nil, line: nil)
|
19
|
+
@filename = filename
|
20
|
+
@line = line
|
21
|
+
super(message)
|
22
|
+
end
|
23
|
+
|
24
|
+
def backtrace
|
25
|
+
return nil if super.nil?
|
26
|
+
sass_backtrace + super
|
27
|
+
end
|
28
|
+
|
29
|
+
# The backtrace of the error within Sass files.
|
30
|
+
def sass_backtrace
|
31
|
+
return [] unless @filename && @line
|
32
|
+
["#{@filename}:#{@line}"]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SassC
|
4
|
+
class FunctionsHandler
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def setup(_native_options, functions: Script::Functions)
|
10
|
+
@callbacks = {}
|
11
|
+
|
12
|
+
functions_wrapper = Class.new do
|
13
|
+
attr_accessor :options
|
14
|
+
|
15
|
+
include functions
|
16
|
+
end.new
|
17
|
+
functions_wrapper.options = @options
|
18
|
+
|
19
|
+
Script.custom_functions(functions: functions).each do |custom_function|
|
20
|
+
callback = lambda do |native_argument_list|
|
21
|
+
function_arguments = arguments_from_native_list(native_argument_list)
|
22
|
+
begin
|
23
|
+
result = functions_wrapper.send(custom_function, *function_arguments)
|
24
|
+
rescue StandardError
|
25
|
+
raise ::Sass::ScriptError, "Error: error in C function #{custom_function}"
|
26
|
+
end
|
27
|
+
to_native_value(result)
|
28
|
+
rescue StandardError => e
|
29
|
+
warn "[SassC::FunctionsHandler] #{e.cause.message}"
|
30
|
+
raise e
|
31
|
+
end
|
32
|
+
|
33
|
+
@callbacks[Script.formatted_function_name(custom_function, functions: functions)] = callback
|
34
|
+
end
|
35
|
+
|
36
|
+
@callbacks
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def arguments_from_native_list(native_argument_list)
|
42
|
+
native_argument_list.map do |native_value|
|
43
|
+
Script::ValueConversion.from_native(native_value, @options)
|
44
|
+
end.compact
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_native_value(sass_value)
|
48
|
+
# if the custom function returns nil, we provide a "default" return
|
49
|
+
# value of an empty string
|
50
|
+
sass_value ||= SassC::Script::Value::String.new("")
|
51
|
+
sass_value.options = @options
|
52
|
+
Script::ValueConversion.to_native(sass_value)
|
53
|
+
end
|
54
|
+
|
55
|
+
def error(message)
|
56
|
+
$stderr.puts "[SassC::FunctionsHandler] #{message}"
|
57
|
+
Native.make_error(message)
|
58
|
+
end
|
59
|
+
|
60
|
+
begin
|
61
|
+
begin
|
62
|
+
raise RuntimeError
|
63
|
+
rescue StandardError
|
64
|
+
raise ::Sass::ScriptError
|
65
|
+
end
|
66
|
+
rescue StandardError => e
|
67
|
+
unless e.full_message.include?(e.cause.full_message)
|
68
|
+
::Sass::ScriptError.class_eval do
|
69
|
+
def full_message(*args, **kwargs)
|
70
|
+
full_message = super(*args, **kwargs)
|
71
|
+
if cause
|
72
|
+
"#{full_message}\n#{cause.full_message(*args, **kwargs)}"
|
73
|
+
else
|
74
|
+
full_message
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SassC
|
4
|
+
class ImportHandler
|
5
|
+
def initialize(options)
|
6
|
+
@importer = if options[:importer]
|
7
|
+
options[:importer].new(options)
|
8
|
+
else
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup(_native_options)
|
14
|
+
Importer.new(@importer) if @importer
|
15
|
+
end
|
16
|
+
|
17
|
+
class FileImporter
|
18
|
+
class << self
|
19
|
+
def resolve_path(path, from_import)
|
20
|
+
ext = File.extname(path)
|
21
|
+
if ['.sass', '.scss', '.css'].include?(ext)
|
22
|
+
if from_import
|
23
|
+
result = exactly_one(try_path("#{without_ext(path)}.import#{ext}"))
|
24
|
+
return result unless result.nil?
|
25
|
+
end
|
26
|
+
return exactly_one(try_path(path))
|
27
|
+
end
|
28
|
+
|
29
|
+
unless ext.empty?
|
30
|
+
if from_import
|
31
|
+
result = exactly_one(try_path("#{without_ext(path)}.import#{ext}"))
|
32
|
+
return result unless result.nil?
|
33
|
+
end
|
34
|
+
result = exactly_one(try_path(path))
|
35
|
+
return result unless result.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
if from_import
|
39
|
+
result = exactly_one(try_path_with_ext("#{path}.import"))
|
40
|
+
return result unless result.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
result = exactly_one(try_path_with_ext(path))
|
44
|
+
return result unless result.nil?
|
45
|
+
|
46
|
+
try_path_as_dir(path, from_import)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def try_path_with_ext(path)
|
52
|
+
result = try_path("#{path}.sass") + try_path("#{path}.scss")
|
53
|
+
result.empty? ? try_path("#{path}.css") : result
|
54
|
+
end
|
55
|
+
|
56
|
+
def try_path(path)
|
57
|
+
partial = File.join(File.dirname(path), "_#{File.basename(path)}")
|
58
|
+
result = []
|
59
|
+
result.push(partial) if file_exist?(partial)
|
60
|
+
result.push(path) if file_exist?(path)
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
def try_path_as_dir(path, from_import)
|
65
|
+
return unless dir_exist? path
|
66
|
+
|
67
|
+
if from_import
|
68
|
+
result = exactly_one(try_path_with_ext(File.join(path, 'index.import')))
|
69
|
+
return result unless result.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
exactly_one(try_path_with_ext(File.join(path, 'index')))
|
73
|
+
end
|
74
|
+
|
75
|
+
def exactly_one(paths)
|
76
|
+
return if paths.empty?
|
77
|
+
return paths.first if paths.length == 1
|
78
|
+
|
79
|
+
raise "It's not clear which file to import. Found:\n#{paths.map { |path| " #{path}" }.join("\n")}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def file_exist?(path)
|
83
|
+
File.exist?(path) && File.file?(path)
|
84
|
+
end
|
85
|
+
|
86
|
+
def dir_exist?(path)
|
87
|
+
File.exist?(path) && File.directory?(path)
|
88
|
+
end
|
89
|
+
|
90
|
+
def without_ext(path)
|
91
|
+
ext = File.extname(path)
|
92
|
+
path.delete_suffix(ext)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private_constant :FileImporter
|
98
|
+
|
99
|
+
class Importer
|
100
|
+
def initialize(importer)
|
101
|
+
@importer = importer
|
102
|
+
|
103
|
+
@canonical_urls = {}
|
104
|
+
@id = 0
|
105
|
+
@importer_results = {}
|
106
|
+
@parent_urls = [URL.path_to_file_url(File.absolute_path(@importer.options[:filename] || 'stdin'))]
|
107
|
+
end
|
108
|
+
|
109
|
+
def canonicalize(url, from_import:)
|
110
|
+
if url.start_with?(Protocol::IMPORT)
|
111
|
+
canonical_url = @canonical_urls.delete(url.delete_prefix(Protocol::IMPORT))
|
112
|
+
unless @importer_results.key?(canonical_url)
|
113
|
+
canonical_url = resolve_file_url(canonical_url, @parent_urls.last, from_import)
|
114
|
+
end
|
115
|
+
@parent_urls.push(canonical_url)
|
116
|
+
canonical_url
|
117
|
+
elsif url.start_with?(Protocol::FILE)
|
118
|
+
path = URL.parse(url).route_from(@parent_urls.last).to_s
|
119
|
+
parent_path = URL.file_url_to_path(@parent_urls.last)
|
120
|
+
|
121
|
+
imports = @importer.imports(path, parent_path)
|
122
|
+
imports = [SassC::Importer::Import.new(path)] if imports.nil?
|
123
|
+
imports = [imports] unless imports.is_a?(Array)
|
124
|
+
imports.each do |import|
|
125
|
+
import.path = File.absolute_path(import.path, File.dirname(parent_path))
|
126
|
+
end
|
127
|
+
|
128
|
+
canonical_url = "#{Protocol::IMPORT}#{next_id}"
|
129
|
+
@importer_results[canonical_url] = imports_to_native(imports)
|
130
|
+
canonical_url
|
131
|
+
elsif url.start_with?(Protocol::LOADED)
|
132
|
+
canonical_url = Protocol::LOADED
|
133
|
+
@parent_urls.pop
|
134
|
+
canonical_url
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def load(canonical_url)
|
139
|
+
if @importer_results.key?(canonical_url)
|
140
|
+
@importer_results.delete(canonical_url)
|
141
|
+
elsif canonical_url.start_with?(Protocol::FILE)
|
142
|
+
path = URL.file_url_to_path(canonical_url)
|
143
|
+
{
|
144
|
+
contents: File.read(path),
|
145
|
+
syntax: syntax(path),
|
146
|
+
source_map_url: canonical_url
|
147
|
+
}
|
148
|
+
elsif canonical_url.start_with?(Protocol::LOADED)
|
149
|
+
{
|
150
|
+
contents: '',
|
151
|
+
syntax: :scss
|
152
|
+
}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def load_paths
|
159
|
+
@load_paths ||= (@importer.options[:load_paths] || []) + SassC.load_paths
|
160
|
+
end
|
161
|
+
|
162
|
+
def resolve_file_url(url, parent_url, from_import)
|
163
|
+
path = URL.parse(url).route_from(parent_url).to_s
|
164
|
+
parent_path = URL.file_url_to_path(parent_url)
|
165
|
+
[File.dirname(parent_path)].concat(load_paths).each do |load_path|
|
166
|
+
resolved = FileImporter.resolve_path(File.absolute_path(path, load_path), from_import)
|
167
|
+
return URL.path_to_file_url(resolved) unless resolved.nil?
|
168
|
+
end
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
|
172
|
+
def syntax(path)
|
173
|
+
case File.extname(path)
|
174
|
+
when '.sass'
|
175
|
+
:indented
|
176
|
+
when '.css'
|
177
|
+
:css
|
178
|
+
else
|
179
|
+
:scss
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def imports_to_native(imports)
|
184
|
+
{
|
185
|
+
contents: imports.flat_map do |import|
|
186
|
+
id = next_id
|
187
|
+
canonical_url = URL.path_to_file_url(import.path)
|
188
|
+
@canonical_urls[id] = canonical_url
|
189
|
+
if import.source
|
190
|
+
@importer_results[canonical_url] = if import.source.is_a?(Hash)
|
191
|
+
{
|
192
|
+
contents: import.source[:contents],
|
193
|
+
syntax: import.source[:syntax],
|
194
|
+
source_map_url: canonical_url
|
195
|
+
}
|
196
|
+
else
|
197
|
+
{
|
198
|
+
contents: import.source,
|
199
|
+
syntax: syntax(import.path),
|
200
|
+
source_map_url: canonical_url
|
201
|
+
}
|
202
|
+
end
|
203
|
+
end
|
204
|
+
[
|
205
|
+
"@import \"#{Protocol::IMPORT}#{id}\";",
|
206
|
+
"@import \"#{Protocol::LOADED}#{id}\";"
|
207
|
+
]
|
208
|
+
end.join("\n"),
|
209
|
+
syntax: :scss
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
def next_id
|
214
|
+
id = @id
|
215
|
+
@id = id.next
|
216
|
+
id.to_s
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
private_constant :Importer
|
221
|
+
end
|
222
|
+
end
|