sass 3.4.0.rc.2 → 3.4.0.rc.3
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/MIT-LICENSE +1 -1
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/lib/sass/engine.rb +1 -0
- data/lib/sass/plugin/compiler.rb +136 -28
- data/lib/sass/script/functions.rb +23 -16
- data/lib/sass/script/lexer.rb +37 -15
- data/lib/sass/script/parser.rb +9 -16
- data/lib/sass/script/value/color.rb +6 -8
- data/lib/sass/shared.rb +1 -1
- data/test/sass/compiler_test.rb +12 -3
- data/test/sass/functions_test.rb +26 -41
- data/test/sass/script_test.rb +23 -6
- data/test/sass/source_map_test.rb +19 -0
- data/test/sass/value_helpers_test.rb +2 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 458976233a2583d8c1a41750182734aeef9a2798
|
4
|
+
data.tar.gz: 5e6bd1f3acb111febce42d566914f3b83b1a1964
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73ad9f9ec78111165eca6460bf5e55baf828580ad272671ca4b31ad1cf6c1cf3566243ea034b7437d4b5a54acd34bf4657fdc3b6c5c383f9f2770901fccf4c09
|
7
|
+
data.tar.gz: 38e64c1843b8a98f1bb640764bd2830c5bf607d33630568585a3b838f11c7bba98bd390aef42e705eb6fee1bd9e3d8a25e5eadc0a5e5129561f3e35dc6410131
|
data/MIT-LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2006-
|
1
|
+
Copyright (c) 2006-2014 Hampton Catlin, Natalie Weizenbaum, and Chris Eppstein
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.4.0.rc.
|
1
|
+
3.4.0.rc.3
|
data/VERSION_DATE
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
02 August 2014 01:42:58 UTC
|
data/lib/sass/engine.rb
CHANGED
data/lib/sass/plugin/compiler.rb
CHANGED
@@ -36,19 +36,30 @@ module Sass::Plugin
|
|
36
36
|
options.merge!(opts)
|
37
37
|
end
|
38
38
|
|
39
|
-
# Register a callback to be run
|
39
|
+
# Register a callback to be run before stylesheets are mass-updated.
|
40
40
|
# This is run whenever \{#update\_stylesheets} is called,
|
41
41
|
# unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
|
42
42
|
# is enabled.
|
43
43
|
#
|
44
|
-
# @yield [
|
45
|
-
# @yieldparam
|
46
|
-
# Individual files to be updated
|
47
|
-
# specified in the options.
|
44
|
+
# @yield [files]
|
45
|
+
# @yieldparam files [<(String, String, String)>]
|
46
|
+
# Individual files to be updated. Files in directories specified are included in this list.
|
48
47
|
# The first element of each pair is the source file,
|
49
|
-
# the second is the target CSS file
|
48
|
+
# the second is the target CSS file,
|
49
|
+
# the third is the target sourcemap file.
|
50
50
|
define_callback :updating_stylesheets
|
51
51
|
|
52
|
+
# Register a callback to be run after stylesheets are mass-updated.
|
53
|
+
# This is run whenever \{#update\_stylesheets} is called,
|
54
|
+
# unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
|
55
|
+
# is enabled.
|
56
|
+
#
|
57
|
+
# @yield [updated_files]
|
58
|
+
# @yieldparam updated_files [<(String, String)>]
|
59
|
+
# Individual files that were updated.
|
60
|
+
# The first element of each pair is the source file, the second is the target CSS file.
|
61
|
+
define_callback :updated_stylesheets
|
62
|
+
|
52
63
|
# Register a callback to be run after a single stylesheet is updated.
|
53
64
|
# The callback is only run if the stylesheet is really updated;
|
54
65
|
# if the CSS file is fresh, this won't be run.
|
@@ -67,6 +78,21 @@ module Sass::Plugin
|
|
67
78
|
# The location of the sourcemap being generated, if any.
|
68
79
|
define_callback :updated_stylesheet
|
69
80
|
|
81
|
+
# Register a callback to be run when compilation starts.
|
82
|
+
#
|
83
|
+
# In combination with on_updated_stylesheet, this could be used
|
84
|
+
# to collect compilation statistics like timing or to take a
|
85
|
+
# diff of the changes to the output file.
|
86
|
+
#
|
87
|
+
# @yield [template, css, sourcemap]
|
88
|
+
# @yieldparam template [String]
|
89
|
+
# The location of the Sass/SCSS file being updated.
|
90
|
+
# @yieldparam css [String]
|
91
|
+
# The location of the CSS file being generated.
|
92
|
+
# @yieldparam sourcemap [String]
|
93
|
+
# The location of the sourcemap being generated, if any.
|
94
|
+
define_callback :compilation_starting
|
95
|
+
|
70
96
|
# Register a callback to be run when Sass decides not to update a stylesheet.
|
71
97
|
# In particular, the callback is run when Sass finds that
|
72
98
|
# the template file and none of its dependencies
|
@@ -139,7 +165,8 @@ module Sass::Plugin
|
|
139
165
|
define_callback :template_deleted
|
140
166
|
|
141
167
|
# Register a callback to be run when Sass deletes a CSS file.
|
142
|
-
# This happens when the corresponding Sass/SCSS file has been deleted
|
168
|
+
# This happens when the corresponding Sass/SCSS file has been deleted
|
169
|
+
# and when the compiler cleans the output files.
|
143
170
|
#
|
144
171
|
# @yield [filename]
|
145
172
|
# @yieldparam filename [String]
|
@@ -147,7 +174,8 @@ module Sass::Plugin
|
|
147
174
|
define_callback :deleting_css
|
148
175
|
|
149
176
|
# Register a callback to be run when Sass deletes a sourcemap file.
|
150
|
-
# This happens when the corresponding Sass/SCSS file has been deleted
|
177
|
+
# This happens when the corresponding Sass/SCSS file has been deleted
|
178
|
+
# and when the compiler cleans the output files.
|
151
179
|
#
|
152
180
|
# @yield [filename]
|
153
181
|
# @yieldparam filename [String]
|
@@ -162,35 +190,72 @@ module Sass::Plugin
|
|
162
190
|
# in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
|
163
191
|
# If it has, it updates the CSS file.
|
164
192
|
#
|
165
|
-
# @param individual_files [Array<(String, String)>]
|
193
|
+
# @param individual_files [Array<(String, String[, String])>]
|
166
194
|
# A list of files to check for updates
|
167
195
|
# **in addition to those specified by the
|
168
196
|
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
169
197
|
# The first string in each pair is the location of the Sass/SCSS file,
|
170
198
|
# the second is the location of the CSS file that it should be compiled to.
|
199
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
171
200
|
def update_stylesheets(individual_files = [])
|
172
|
-
individual_files = individual_files.dup
|
173
201
|
Sass::Plugin.checked_for_updates = true
|
174
202
|
staleness_checker = StalenessChecker.new(engine_options)
|
175
203
|
|
176
|
-
|
177
|
-
|
178
|
-
# Get the relative path to the file
|
179
|
-
name = file.sub(template_location.to_s.sub(/\/*$/, '/'), "")
|
180
|
-
css = css_filename(name, css_location)
|
181
|
-
sourcemap = Sass::Util.sourcemap_name(css) if engine_options[:sourcemap] != :none
|
182
|
-
individual_files << [file, css, sourcemap]
|
183
|
-
end
|
184
|
-
end
|
204
|
+
files = file_list(individual_files)
|
205
|
+
run_updating_stylesheets(files)
|
185
206
|
|
186
|
-
|
207
|
+
updated_stylesheets = []
|
208
|
+
files.each do |file, css, sourcemap|
|
187
209
|
# TODO: Does staleness_checker need to check the sourcemap file as well?
|
188
210
|
if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
|
211
|
+
# XXX For consistency, this should return the sourcemap too, but it would
|
212
|
+
# XXX be an API change.
|
213
|
+
updated_stylesheets << [file, css]
|
189
214
|
update_stylesheet(file, css, sourcemap)
|
190
215
|
else
|
191
216
|
run_not_updating_stylesheet(file, css, sourcemap)
|
192
217
|
end
|
193
218
|
end
|
219
|
+
run_updated_stylesheets(updated_stylesheets)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Construct a list of files that might need to be compiled
|
223
|
+
# from the provided individual_files and the template_locations.
|
224
|
+
#
|
225
|
+
# Note: this method does not cache the results as they can change
|
226
|
+
# across invocations when sass files are added or removed.
|
227
|
+
#
|
228
|
+
# @param individual_files [Array<(String, String[, String])>]
|
229
|
+
# A list of files to check for updates
|
230
|
+
# **in addition to those specified by the
|
231
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
232
|
+
# The first string in each pair is the location of the Sass/SCSS file,
|
233
|
+
# the second is the location of the CSS file that it should be compiled to.
|
234
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
235
|
+
# @return [Array<(String, String, String)>]
|
236
|
+
# A list of [sass_file, css_file, sourcemap_file] tuples similar
|
237
|
+
# to what was passed in, but expanded to include the current state
|
238
|
+
# of the directories being updated.
|
239
|
+
def file_list(individual_files = [])
|
240
|
+
files = individual_files.map do |tuple|
|
241
|
+
if tuple.size < 3
|
242
|
+
[tuple[0], tuple[1], Sass::Util.sourcemap_name(tuple[1])]
|
243
|
+
else
|
244
|
+
tuple
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
template_location_array.each do |template_location, css_location|
|
249
|
+
Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
|
250
|
+
# Get the relative path to the file
|
251
|
+
name = Sass::Util.pathname(file).relative_path_from(
|
252
|
+
Sass::Util.pathname(template_location.to_s)).to_s
|
253
|
+
css = css_filename(name, css_location)
|
254
|
+
sourcemap = Sass::Util.sourcemap_name(css) unless engine_options[:sourcemap] == :none
|
255
|
+
files << [file, css, sourcemap]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
files
|
194
259
|
end
|
195
260
|
|
196
261
|
# Watches the template directory (or directories)
|
@@ -211,14 +276,19 @@ module Sass::Plugin
|
|
211
276
|
# The version of Listen distributed with Sass is loaded by default,
|
212
277
|
# but if another version has already been loaded that will be used instead.
|
213
278
|
#
|
214
|
-
# @param individual_files [Array<(String, String)>]
|
215
|
-
# A list of files to
|
279
|
+
# @param individual_files [Array<(String, String[, String])>]
|
280
|
+
# A list of files to check for updates
|
216
281
|
# **in addition to those specified by the
|
217
282
|
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
218
283
|
# The first string in each pair is the location of the Sass/SCSS file,
|
219
284
|
# the second is the location of the CSS file that it should be compiled to.
|
220
|
-
|
221
|
-
|
285
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
286
|
+
# @param options [Hash] The options that control how watching works.
|
287
|
+
# @option options [Boolean] :skip_initial_update
|
288
|
+
# Don't do an initial update when starting the watcher when true
|
289
|
+
def watch(individual_files = [], options = {})
|
290
|
+
options, individual_files = individual_files, [] if individual_files.is_a?(Hash)
|
291
|
+
update_stylesheets(individual_files) unless options[:skip_initial_update]
|
222
292
|
|
223
293
|
directories = watched_paths
|
224
294
|
individual_files.each do |(source, _, _)|
|
@@ -235,7 +305,11 @@ module Sass::Plugin
|
|
235
305
|
|
236
306
|
# TODO: Keep better track of what depends on what
|
237
307
|
# so we don't have to run a global update every time anything changes.
|
238
|
-
|
308
|
+
# XXX The :additional_watch_paths option exists for Compass to use until
|
309
|
+
# a deprecated feature is removed. It may be removed without warning.
|
310
|
+
listener_args = directories +
|
311
|
+
Array(options[:additional_watch_paths]) +
|
312
|
+
[{:relative_paths => false}]
|
239
313
|
|
240
314
|
# The native windows listener is much slower than the polling option, according to
|
241
315
|
# https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e
|
@@ -248,6 +322,7 @@ module Sass::Plugin
|
|
248
322
|
|
249
323
|
listener = create_listener(*listener_args) do |modified, added, removed|
|
250
324
|
on_file_changed(individual_files, modified, added, removed)
|
325
|
+
yield(modified, added, removed) if block_given?
|
251
326
|
end
|
252
327
|
|
253
328
|
if poll && !Sass::Util.listen_geq_2?
|
@@ -267,6 +342,8 @@ module Sass::Plugin
|
|
267
342
|
def engine_options(additional_options = {})
|
268
343
|
opts = options.merge(additional_options)
|
269
344
|
opts[:load_paths] = load_paths(opts)
|
345
|
+
options[:sourcemap] = :auto if options[:sourcemap] == true
|
346
|
+
options[:sourcemap] = :none if options[:sourcemap] == false
|
270
347
|
opts
|
271
348
|
end
|
272
349
|
|
@@ -275,12 +352,42 @@ module Sass::Plugin
|
|
275
352
|
StalenessChecker.stylesheet_needs_update?(css_file, template_file)
|
276
353
|
end
|
277
354
|
|
355
|
+
# Remove all output files that would be created by calling update_stylesheets, if they exist.
|
356
|
+
#
|
357
|
+
# This method runs the deleting_css and deleting_sourcemap callbacks for
|
358
|
+
# the files that are deleted.
|
359
|
+
#
|
360
|
+
# @param individual_files [Array<(String, String[, String])>]
|
361
|
+
# A list of files to check for updates
|
362
|
+
# **in addition to those specified by the
|
363
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
364
|
+
# The first string in each pair is the location of the Sass/SCSS file,
|
365
|
+
# the second is the location of the CSS file that it should be compiled to.
|
366
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
367
|
+
def clean(individual_files = [])
|
368
|
+
file_list(individual_files).each do |(_, css_file, sourcemap_file)|
|
369
|
+
if File.exist?(css_file)
|
370
|
+
run_deleting_css css_file
|
371
|
+
File.delete(css_file)
|
372
|
+
end
|
373
|
+
if sourcemap_file && File.exist?(sourcemap_file)
|
374
|
+
run_deleting_sourcemap sourcemap_file
|
375
|
+
File.delete(sourcemap_file)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
nil
|
379
|
+
end
|
380
|
+
|
278
381
|
private
|
279
382
|
|
280
383
|
def create_listener(*args, &block)
|
281
384
|
Sass::Util.load_listen!
|
282
385
|
if Sass::Util.listen_geq_2?
|
283
|
-
|
386
|
+
# Work around guard/listen#243.
|
387
|
+
options = args.pop if args.last.is_a?(Hash)
|
388
|
+
args.map do |dir|
|
389
|
+
Listen.to(dir, options, &block)
|
390
|
+
end
|
284
391
|
else
|
285
392
|
Listen::Listener.new(*args, &block)
|
286
393
|
end
|
@@ -288,7 +395,7 @@ module Sass::Plugin
|
|
288
395
|
|
289
396
|
def listen_to(listener)
|
290
397
|
if Sass::Util.listen_geq_2?
|
291
|
-
listener.start.join
|
398
|
+
listener.map {|l| l.start}.each {|thread| thread.join}
|
292
399
|
else
|
293
400
|
listener.start!
|
294
401
|
end
|
@@ -328,6 +435,7 @@ module Sass::Plugin
|
|
328
435
|
end
|
329
436
|
|
330
437
|
removed.uniq.each do |f|
|
438
|
+
run_template_deleted(relative_to_pwd(f))
|
331
439
|
if (files = individual_files.find {|(source, _, _)| File.expand_path(source) == f})
|
332
440
|
recompile_required = true
|
333
441
|
# This was a file we were watching explicitly and compiling to a particular location.
|
@@ -347,7 +455,6 @@ module Sass::Plugin
|
|
347
455
|
end
|
348
456
|
end
|
349
457
|
end
|
350
|
-
run_template_deleted(relative_to_pwd(f))
|
351
458
|
end
|
352
459
|
|
353
460
|
if recompile_required
|
@@ -371,6 +478,7 @@ module Sass::Plugin
|
|
371
478
|
:filename => filename,
|
372
479
|
:sourcemap_filename => sourcemap)
|
373
480
|
mapping = nil
|
481
|
+
run_compilation_starting(filename, css, sourcemap)
|
374
482
|
engine = Sass::Engine.for_file(filename, engine_opts)
|
375
483
|
if sourcemap
|
376
484
|
rendered, mapping = engine.render_with_sourcemap(File.basename(sourcemap))
|
@@ -628,10 +628,9 @@ module Sass::Script
|
|
628
628
|
|
629
629
|
color_attrs = [[red, :red], [green, :green], [blue, :blue]].map do |(c, name)|
|
630
630
|
if c.is_unit?("%")
|
631
|
-
|
632
|
-
v * 255 / 100.0
|
631
|
+
c.value * 255 / 100.0
|
633
632
|
elsif c.unitless?
|
634
|
-
|
633
|
+
c.value
|
635
634
|
else
|
636
635
|
raise ArgumentError.new("Expected #{c} to be unitless or have a unit of % but got #{c}")
|
637
636
|
end
|
@@ -683,7 +682,6 @@ module Sass::Script
|
|
683
682
|
assert_type color, :Color, :color
|
684
683
|
assert_type alpha, :Number, :alpha
|
685
684
|
|
686
|
-
Sass::Util.check_range('Alpha channel', 0..1, alpha)
|
687
685
|
color.with(:alpha => alpha.value)
|
688
686
|
when 4
|
689
687
|
red, green, blue, alpha = args
|
@@ -741,11 +739,9 @@ module Sass::Script
|
|
741
739
|
assert_type lightness, :Number, :lightness
|
742
740
|
assert_type alpha, :Number, :alpha
|
743
741
|
|
744
|
-
Sass::Util.check_range('Alpha channel', 0..1, alpha)
|
745
|
-
|
746
742
|
h = hue.value
|
747
|
-
s =
|
748
|
-
l =
|
743
|
+
s = saturation.value
|
744
|
+
l = lightness.value
|
749
745
|
|
750
746
|
# Don't store the string representation for function-created colors, both
|
751
747
|
# because it's not very useful and because some functions aren't supported
|
@@ -1243,12 +1239,27 @@ module Sass::Script
|
|
1243
1239
|
# same time
|
1244
1240
|
def change_color(color, kwargs)
|
1245
1241
|
assert_type color, :Color, :color
|
1246
|
-
with = Sass::Util.
|
1242
|
+
with = Sass::Util.map_hash(
|
1243
|
+
'red' => ['Red value', 0..255],
|
1244
|
+
'green' => ['Green value', 0..255],
|
1245
|
+
'blue' => ['Blue value', 0..255],
|
1246
|
+
'hue' => [],
|
1247
|
+
'saturation' => ['Saturation', 0..100, '%'],
|
1248
|
+
'lightness' => ['Lightness', 0..100, '%'],
|
1249
|
+
'alpha' => ['Alpha channel', 0..1]
|
1250
|
+
) do |name, (desc, range, unit)|
|
1247
1251
|
val = kwargs.delete(name)
|
1248
1252
|
next unless val
|
1249
1253
|
assert_type val, :Number, name
|
1250
|
-
|
1251
|
-
|
1254
|
+
|
1255
|
+
if range
|
1256
|
+
val = Sass::Util.check_range(desc, range, val, unit)
|
1257
|
+
else
|
1258
|
+
val = val.value
|
1259
|
+
end
|
1260
|
+
|
1261
|
+
[name.to_sym, val]
|
1262
|
+
end
|
1252
1263
|
|
1253
1264
|
unless kwargs.empty?
|
1254
1265
|
name, val = kwargs.to_a.first
|
@@ -2609,11 +2620,7 @@ module Sass::Script
|
|
2609
2620
|
assert_type amount, :Number, :amount
|
2610
2621
|
Sass::Util.check_range('Amount', range, amount, units)
|
2611
2622
|
|
2612
|
-
|
2613
|
-
# or should we do so in the Color constructor itself,
|
2614
|
-
# and allow clipping in rgb() et al?
|
2615
|
-
color.with(attr => Sass::Util.restrict(
|
2616
|
-
color.send(attr).send(op, amount.value), range))
|
2623
|
+
color.with(attr => color.send(attr).send(op, amount.value))
|
2617
2624
|
end
|
2618
2625
|
end
|
2619
2626
|
end
|
data/lib/sass/script/lexer.rb
CHANGED
@@ -250,8 +250,14 @@ module Sass
|
|
250
250
|
end
|
251
251
|
|
252
252
|
def token
|
253
|
-
if after_interpolation? && (
|
254
|
-
|
253
|
+
if after_interpolation? && (interp = @interpolation_stack.pop)
|
254
|
+
interp_type, interp_value = interp
|
255
|
+
if interp_type == :special_fun
|
256
|
+
return special_fun_body(interp_value)
|
257
|
+
else
|
258
|
+
raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string
|
259
|
+
return string(interp_value, true)
|
260
|
+
end
|
255
261
|
end
|
256
262
|
|
257
263
|
variable || string(:double, false) || string(:single, false) || number || id || color ||
|
@@ -289,7 +295,7 @@ MESSAGE
|
|
289
295
|
if @scanner[2] == '#{' # '
|
290
296
|
@scanner.pos -= 2 # Don't actually consume the #{
|
291
297
|
@offset -= 2
|
292
|
-
@interpolation_stack << re
|
298
|
+
@interpolation_stack << [:string, re]
|
293
299
|
end
|
294
300
|
str =
|
295
301
|
if re == :uri
|
@@ -368,18 +374,34 @@ MESSAGE
|
|
368
374
|
end
|
369
375
|
|
370
376
|
def special_fun
|
371
|
-
|
372
|
-
return unless
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
377
|
+
prefix = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
|
378
|
+
return unless prefix
|
379
|
+
special_fun_body(1, prefix)
|
380
|
+
end
|
381
|
+
|
382
|
+
def special_fun_body(parens, prefix = nil)
|
383
|
+
str = prefix || ''
|
384
|
+
while (scanned = scan(/.*?([()]|\#{)/m))
|
385
|
+
str << scanned
|
386
|
+
if scanned[-1] == ?(
|
387
|
+
parens += 1
|
388
|
+
next
|
389
|
+
elsif scanned[-1] == ?)
|
390
|
+
parens -= 1
|
391
|
+
next unless parens == 0
|
392
|
+
else
|
393
|
+
raise "[BUG] Unreachable" unless @scanner[1] == '#{' # '
|
394
|
+
str.slice!(-2..-1)
|
395
|
+
@scanner.pos -= 2 # Don't actually consume the #{
|
396
|
+
@offset -= 2
|
397
|
+
@interpolation_stack << [:special_fun, parens]
|
398
|
+
end
|
399
|
+
|
400
|
+
return [:special_fun, Sass::Script::Value::String.new(str)]
|
401
|
+
end
|
402
|
+
|
403
|
+
scan(/.*/)
|
404
|
+
expected!('")"')
|
383
405
|
end
|
384
406
|
|
385
407
|
def special_val
|
data/lib/sass/script/parser.rb
CHANGED
@@ -494,22 +494,14 @@ RUBY
|
|
494
494
|
end
|
495
495
|
|
496
496
|
def special_fun
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
node(
|
506
|
-
Script::Tree::Interpolation.new(
|
507
|
-
l, i,
|
508
|
-
r && literal_node(Script::Value::String.new(r),
|
509
|
-
i.source_range.end_pos, end_pos),
|
510
|
-
false, false),
|
511
|
-
start_pos, end_pos)
|
512
|
-
end
|
497
|
+
first = try_tok(:special_fun)
|
498
|
+
return paren unless first
|
499
|
+
str = literal_node(first.value, first.source_range)
|
500
|
+
return str unless try_tok(:begin_interpolation)
|
501
|
+
mid = parse_interpolated
|
502
|
+
last = assert_expr(:special_fun)
|
503
|
+
node(Tree::Interpolation.new(str, mid, last, false, false),
|
504
|
+
first.source_range.start_pos)
|
513
505
|
end
|
514
506
|
|
515
507
|
def paren
|
@@ -571,6 +563,7 @@ RUBY
|
|
571
563
|
:mixin_arglist => "mixin argument",
|
572
564
|
:fn_arglist => "function argument",
|
573
565
|
:splat => "...",
|
566
|
+
:special_fun => '")"',
|
574
567
|
}
|
575
568
|
|
576
569
|
def assert_expr(name, expected = nil)
|
@@ -196,9 +196,9 @@ module Sass::Script::Value
|
|
196
196
|
# Constructs an RGB or HSL color object,
|
197
197
|
# optionally with an alpha channel.
|
198
198
|
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
# The alpha value
|
199
|
+
# RGB values are clipped within 0 and 255.
|
200
|
+
# Saturation and lightness values are clipped within 0 and 100.
|
201
|
+
# The alpha value is clipped within 0 and 1.
|
202
202
|
#
|
203
203
|
# @raise [Sass::SyntaxError] if any color value isn't in the specified range
|
204
204
|
#
|
@@ -258,17 +258,15 @@ module Sass::Script::Value
|
|
258
258
|
|
259
259
|
[:red, :green, :blue].each do |k|
|
260
260
|
next if @attrs[k].nil?
|
261
|
-
@attrs[k] = @attrs[k].to_i
|
262
|
-
Sass::Util.check_range("#{k.to_s.capitalize} value", 0..255, @attrs[k])
|
261
|
+
@attrs[k] = Sass::Util.restrict(@attrs[k].to_i, 0..255)
|
263
262
|
end
|
264
263
|
|
265
264
|
[:saturation, :lightness].each do |k|
|
266
265
|
next if @attrs[k].nil?
|
267
|
-
|
268
|
-
@attrs[k] = Sass::Util.check_range("#{k.to_s.capitalize}", 0..100, value, '%')
|
266
|
+
@attrs[k] = Sass::Util.restrict(@attrs[k], 0..100)
|
269
267
|
end
|
270
268
|
|
271
|
-
@attrs[:alpha] = Sass::Util.
|
269
|
+
@attrs[:alpha] = Sass::Util.restrict(@attrs[:alpha], 0..1)
|
272
270
|
end
|
273
271
|
|
274
272
|
# Create a new color from a valid CSS hex string.
|
data/lib/sass/shared.rb
CHANGED
data/test/sass/compiler_test.rb
CHANGED
@@ -82,9 +82,18 @@ class CompilerTest < MiniTest::Test
|
|
82
82
|
|
83
83
|
private
|
84
84
|
def create_listener(*args, &on_filesystem_event)
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
if Sass::Util.listen_geq_2?
|
86
|
+
options = args.pop if args.last.is_a?(Hash)
|
87
|
+
args.map do |dir|
|
88
|
+
@fake_listener = FakeListener.new(*args, &on_filesystem_event)
|
89
|
+
@fake_listener.on_start!(&run_during_start)
|
90
|
+
@fake_listener
|
91
|
+
end
|
92
|
+
else
|
93
|
+
@fake_listener = FakeListener.new(*args, &on_filesystem_event)
|
94
|
+
@fake_listener.on_start!(&run_during_start)
|
95
|
+
@fake_listener
|
96
|
+
end
|
88
97
|
end
|
89
98
|
end
|
90
99
|
|
data/test/sass/functions_test.rb
CHANGED
@@ -90,9 +90,9 @@ class SassFunctionTest < MiniTest::Test
|
|
90
90
|
assert_equal "#33cccc", evaluate("hsl($hue: 180, $saturation: 60%, $lightness: 50%)")
|
91
91
|
end
|
92
92
|
|
93
|
-
def
|
94
|
-
|
95
|
-
|
93
|
+
def test_hsl_clamps_bounds
|
94
|
+
assert_equal("#1f1f1f", evaluate("hsl(10, -114, 12)"))
|
95
|
+
assert_equal("white", evaluate("hsl(10, 10, 256%)"))
|
96
96
|
end
|
97
97
|
|
98
98
|
def test_hsl_checks_types
|
@@ -108,11 +108,11 @@ class SassFunctionTest < MiniTest::Test
|
|
108
108
|
assert_equal "rgba(51, 204, 204, 0.4)", evaluate("hsla($hue: 180, $saturation: 60%, $lightness: 50%, $alpha: 0.4)")
|
109
109
|
end
|
110
110
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
111
|
+
def test_hsla_clamps_bounds
|
112
|
+
assert_equal("#1f1f1f", evaluate("hsla(10, -114, 12, 1)"))
|
113
|
+
assert_equal("rgba(255, 255, 255, 0)", evaluate("hsla(10, 10, 256%, 0)"))
|
114
|
+
assert_equal("rgba(28, 24, 23, 0)", evaluate("hsla(10, 10, 10, -0.1)"))
|
115
|
+
assert_equal("#1c1817", evaluate("hsla(10, 10, 10, 1.1)"))
|
116
116
|
end
|
117
117
|
|
118
118
|
def test_hsla_checks_types
|
@@ -212,26 +212,18 @@ class SassFunctionTest < MiniTest::Test
|
|
212
212
|
assert_equal("springgreen", evaluate("rgb(0%, 100%, 50%)"))
|
213
213
|
end
|
214
214
|
|
215
|
-
def
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
"rgb(1, 1, 256)")
|
222
|
-
assert_error_message("$green: Color value 256 must be between 0 and 255 for `rgb'",
|
223
|
-
"rgb(1, 256, 257)")
|
224
|
-
assert_error_message("$red: Color value -1 must be between 0 and 255 for `rgb'",
|
225
|
-
"rgb(-1, 1, 1)")
|
215
|
+
def test_rgb_clamps_bounds
|
216
|
+
assert_equal("#ff0101", evaluate("rgb(256, 1, 1)"))
|
217
|
+
assert_equal("#01ff01", evaluate("rgb(1, 256, 1)"))
|
218
|
+
assert_equal("#0101ff", evaluate("rgb(1, 1, 256)"))
|
219
|
+
assert_equal("#01ffff", evaluate("rgb(1, 256, 257)"))
|
220
|
+
assert_equal("#000101", evaluate("rgb(-1, 1, 1)"))
|
226
221
|
end
|
227
222
|
|
228
|
-
def
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
"rgb(0, -0.1%, 0)")
|
233
|
-
assert_error_message("$blue: Color value 101% must be between 0% and 100% for `rgb'",
|
234
|
-
"rgb(0, 0, 101%)")
|
223
|
+
def test_rgb_clamps_percent_bounds
|
224
|
+
assert_equal("red", evaluate("rgb(100.1%, 0, 0)"))
|
225
|
+
assert_equal("black", evaluate("rgb(0, -0.1%, 0)"))
|
226
|
+
assert_equal("blue", evaluate("rgb(0, 0, 101%)"))
|
235
227
|
end
|
236
228
|
|
237
229
|
def test_rgb_tests_types
|
@@ -247,21 +239,14 @@ class SassFunctionTest < MiniTest::Test
|
|
247
239
|
assert_equal("rgba(0, 255, 127, 0)", evaluate("rgba($red: 0, $green: 255, $blue: 127, $alpha: 0)"))
|
248
240
|
end
|
249
241
|
|
250
|
-
def
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
"rgba(1, 256, 257, 0.3)")
|
259
|
-
assert_error_message("$red: Color value -1 must be between 0 and 255 for `rgba'",
|
260
|
-
"rgba(-1, 1, 1, 0.3)")
|
261
|
-
assert_error_message("Alpha channel -0.2 must be between 0 and 1 for `rgba'",
|
262
|
-
"rgba(1, 1, 1, -0.2)")
|
263
|
-
assert_error_message("Alpha channel 1.2 must be between 0 and 1 for `rgba'",
|
264
|
-
"rgba(1, 1, 1, 1.2)")
|
242
|
+
def test_rgba_clamps_bounds
|
243
|
+
assert_equal("rgba(255, 1, 1, 0.3)", evaluate("rgba(256, 1, 1, 0.3)"))
|
244
|
+
assert_equal("rgba(1, 255, 1, 0.3)", evaluate("rgba(1, 256, 1, 0.3)"))
|
245
|
+
assert_equal("rgba(1, 1, 255, 0.3)", evaluate("rgba(1, 1, 256, 0.3)"))
|
246
|
+
assert_equal("rgba(1, 255, 255, 0.3)", evaluate("rgba(1, 256, 257, 0.3)"))
|
247
|
+
assert_equal("rgba(0, 1, 1, 0.3)", evaluate("rgba(-1, 1, 1, 0.3)"))
|
248
|
+
assert_equal("rgba(1, 1, 1, 0)", evaluate("rgba(1, 1, 1, -0.2)"))
|
249
|
+
assert_equal("#010101", evaluate("rgba(1, 1, 1, 1.2)"))
|
265
250
|
end
|
266
251
|
|
267
252
|
def test_rgba_tests_types
|
data/test/sass/script_test.rb
CHANGED
@@ -21,14 +21,14 @@ end
|
|
21
21
|
class SassScriptTest < MiniTest::Test
|
22
22
|
include Sass::Script
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
24
|
+
def test_color_clamps_input
|
25
|
+
assert_equal 0, Sass::Script::Value::Color.new([1, 2, -1]).blue
|
26
|
+
assert_equal 255, Sass::Script::Value::Color.new([256, 2, 3]).red
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
29
|
+
def test_color_clamps_rgba_input
|
30
|
+
assert_equal 1, Sass::Script::Value::Color.new([1, 2, 3, 1.1]).alpha
|
31
|
+
assert_equal 0, Sass::Script::Value::Color.new([1, 2, 3, -0.1]).alpha
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_string_escapes
|
@@ -838,6 +838,23 @@ SCSS
|
|
838
838
|
assert_equal 'foobar', resolve("'foo\\\nbar'")
|
839
839
|
end
|
840
840
|
|
841
|
+
def test_unclosed_special_fun
|
842
|
+
assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "calc(foo()": expected ")", was ""') do
|
843
|
+
resolve("calc(foo()")
|
844
|
+
end
|
845
|
+
assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "calc(#{\')\'}": expected ")", was ""') do
|
846
|
+
resolve("calc(\#{')'}")
|
847
|
+
end
|
848
|
+
assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "calc(#{foo": expected "}", was ""') do
|
849
|
+
resolve("calc(\#{foo")
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
def test_special_fun_with_interpolation
|
854
|
+
assert_equal "calc())", resolve("calc(\#{')'})")
|
855
|
+
assert_equal "calc(# {foo})", resolve("calc(# {foo})")
|
856
|
+
end
|
857
|
+
|
841
858
|
# Regression Tests
|
842
859
|
|
843
860
|
def test_repeatedly_modified_color
|
@@ -790,6 +790,25 @@ SCSS
|
|
790
790
|
CSS
|
791
791
|
end
|
792
792
|
|
793
|
+
def test_multiline_interpolation_source_range
|
794
|
+
engine = Sass::Engine.new(<<-SCSS, cache: false, syntax: :scss)
|
795
|
+
p {
|
796
|
+
filter: progid:DXImageTransform(
|
797
|
+
'\#{123}');
|
798
|
+
}
|
799
|
+
SCSS
|
800
|
+
|
801
|
+
interpolated = engine.to_tree.children.
|
802
|
+
first.children.
|
803
|
+
first.value.children[1]
|
804
|
+
assert_equal interpolated.to_sass, "\#{123}"
|
805
|
+
range = interpolated.source_range
|
806
|
+
assert_equal 3, range.start_pos.line
|
807
|
+
assert_equal 12, range.start_pos.offset
|
808
|
+
assert_equal 3, range.end_pos.line
|
809
|
+
assert_equal 18, range.end_pos.offset
|
810
|
+
end
|
811
|
+
|
793
812
|
def test_sources_array_is_uri_escaped
|
794
813
|
map = Sass::Source::Map.new
|
795
814
|
importer = Sass::Importers::Filesystem.new('.')
|
@@ -51,10 +51,8 @@ class ValueHelpersTest < MiniTest::Test
|
|
51
51
|
assert_equal 0.5, color_with_alpha.alpha
|
52
52
|
end
|
53
53
|
|
54
|
-
def
|
55
|
-
|
56
|
-
hex_color("FF007F", 50)
|
57
|
-
end
|
54
|
+
def test_hex_color_alpha_clamps_0_to_1
|
55
|
+
assert_equal 1, hex_color("FF007F", 50).alpha
|
58
56
|
end
|
59
57
|
|
60
58
|
def test_hsl_color_without_alpha
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sass
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.4.0.rc.
|
4
|
+
version: 3.4.0.rc.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Natalie Weizenbaum
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-
|
13
|
+
date: 2014-08-02 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: yard
|