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 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
+ [![build](https://github.com/tablecheck/dartsass-ruby/actions/workflows/build.yml/badge.svg)](https://github.com/tablecheck/dartsass-ruby/actions/workflows/build.yml)
4
+ [![gem](https://badge.fury.io/rb/dartsass-ruby.svg)](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,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "sassc"
@@ -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
@@ -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
@@ -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