sass 3.1.16 → 3.1.17

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.
data/Rakefile CHANGED
@@ -51,7 +51,7 @@ task :permissions do
51
51
  end
52
52
 
53
53
  task :revision_file do
54
- require 'lib/sass'
54
+ require scope('lib/sass')
55
55
 
56
56
  release = Rake.application.top_level_tasks.include?('release') || File.exist?(scope('EDGE_GEM_VERSION'))
57
57
  if Sass.version[:rev] && !release
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.16
1
+ 3.1.17
@@ -452,7 +452,7 @@ MSG
452
452
  def probably_dest_dir?(path)
453
453
  return false unless path
454
454
  return false if colon_path?(path)
455
- return Dir.glob(File.join(path, "*.s[ca]ss")).empty?
455
+ return Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
456
456
  end
457
457
  end
458
458
 
@@ -584,7 +584,7 @@ END
584
584
  end
585
585
 
586
586
  ext = @options[:from]
587
- Dir.glob("#{@options[:input]}/**/*.#{ext}") do |f|
587
+ Sass::Util.glob("#{@options[:input]}/**/*.#{ext}") do |f|
588
588
  output =
589
589
  if @options[:in_place]
590
590
  f
@@ -5,7 +5,6 @@ require 'sass'
5
5
  require 'sass/callbacks'
6
6
  require 'sass/plugin/configuration'
7
7
  require 'sass/plugin/staleness_checker'
8
- require 'sass/plugin/listener'
9
8
 
10
9
  module Sass::Plugin
11
10
 
@@ -188,7 +187,7 @@ module Sass::Plugin
188
187
  staleness_checker = StalenessChecker.new(engine_options)
189
188
 
190
189
  template_location_array.each do |template_location, css_location|
191
- Dir.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
190
+ Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
192
191
  # Get the relative path to the file
193
192
  name = file.sub(template_location.to_s.sub(/\/*$/, '/'), "")
194
193
  css = css_filename(name, css_location)
@@ -252,52 +251,61 @@ module Sass::Plugin
252
251
  end
253
252
  end
254
253
 
254
+ template_paths = template_locations # cache the locations
255
+ individual_files_hash = individual_files.inject({}) do |h, files|
256
+ parent = File.dirname(files.first)
257
+ (h[parent] ||= []) << files unless template_paths.include?(parent)
258
+ h
259
+ end
260
+ directories = template_paths + individual_files_hash.keys +
261
+ [{:relative_paths => true}]
262
+
255
263
  # TODO: Keep better track of what depends on what
256
264
  # so we don't have to run a global update every time anything changes.
257
- Sass::Plugin::Listener.new do |l|
258
- template_location_array.each do |template_location, css_location|
259
- l.directory(template_location, {
260
- :modified => lambda do |base, relative|
261
- next if relative !~ /\.s[ac]ss$/
262
- run_template_modified File.join(base, relative)
263
- update_stylesheets(individual_files)
264
- end,
265
-
266
- :added => lambda do |base, relative|
267
- next if relative !~ /\.s[ac]ss$/
268
- run_template_created File.join(base, relative)
269
- update_stylesheets(individual_files)
270
- end,
271
-
272
- :removed => lambda do |base, relative|
273
- next if relative !~ /\.s[ac]ss$/
274
- run_template_deleted File.join(base, relative)
275
- css = File.join(css_location, relative.gsub(/\.s[ac]ss$/, '.css'))
276
- try_delete_css css
277
- update_stylesheets(individual_files)
278
- end
279
- })
265
+ listener = Listen::MultiListener.new(*directories) do |modified, added, removed|
266
+ modified.each do |f|
267
+ parent = File.dirname(f)
268
+ if files = individual_files_hash[parent]
269
+ next unless files.first == f
270
+ else
271
+ next unless f =~ /\.s[ac]ss$/
272
+ end
273
+ run_template_modified(f)
280
274
  end
281
275
 
282
- individual_files.each do |template, css|
283
- l.file(template, {
284
- :modified => lambda do
285
- run_template_modified template
286
- update_stylesheets(individual_files)
287
- end,
288
-
289
- :added => lambda do
290
- run_template_created template
291
- update_stylesheets(individual_files)
292
- end,
293
-
294
- :removed => lambda do
295
- run_template_deleted template
296
- try_delete_css css
297
- update_stylesheets(individual_files)
298
- end
299
- })
276
+ added.each do |f|
277
+ parent = File.dirname(f)
278
+ if files = individual_files_hash[parent]
279
+ next unless files.first == f
280
+ else
281
+ next unless f =~ /\.s[ac]ss$/
282
+ end
283
+ run_template_created(f)
284
+ end
285
+
286
+ removed.each do |f|
287
+ parent = File.dirname(f)
288
+ if files = individual_files_hash[parent]
289
+ next unless files.first == f
290
+ try_delete_css files[1]
291
+ else
292
+ next unless f =~ /\.s[ac]ss$/
293
+ try_delete_css f.gsub(/\.s[ac]ss$/, '.css')
294
+ end
295
+ run_template_deleted(f)
300
296
  end
297
+
298
+ update_stylesheets(individual_files)
299
+ end
300
+
301
+ # The native windows listener is much slower than the polling
302
+ # option, according to https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e#commitcomment-1295118
303
+ listener.force_polling(true) if Sass::Util.windows?
304
+
305
+ begin
306
+ listener.start
307
+ rescue Exception => e
308
+ raise e unless e.is_a?(Interrupt)
301
309
  end
302
310
  end
303
311
 
@@ -101,21 +101,16 @@ module Sass::Script
101
101
  [:red, :green, :blue].each do |k|
102
102
  next if @attrs[k].nil?
103
103
  @attrs[k] = @attrs[k].to_i
104
- next if (0..255).include?(@attrs[k])
105
- raise ArgumentError.new("#{k.to_s.capitalize} value must be between 0 and 255")
104
+ Sass::Util.check_range("#{k.to_s.capitalize} value", 0..255, @attrs[k])
106
105
  end
107
106
 
108
107
  [:saturation, :lightness].each do |k|
109
108
  next if @attrs[k].nil?
110
- @attrs[k] = 0 if @attrs[k] < 0.00001 && @attrs[k] > -0.00001
111
- @attrs[k] = 100 if @attrs[k] - 100 < 0.00001 && @attrs[k] - 100 > -0.00001
112
- next if (0..100).include?(@attrs[k])
113
- raise ArgumentError.new("#{k.to_s.capitalize} must be between 0 and 100")
109
+ value = Number.new(@attrs[k], ['%']) # Get correct unit for error messages
110
+ @attrs[k] = Sass::Util.check_range("#{k.to_s.capitalize}", 0..100, value, '%')
114
111
  end
115
112
 
116
- unless (0..1).include?(@attrs[:alpha])
117
- raise ArgumentError.new("Alpha channel must be between 0 and 1")
118
- end
113
+ @attrs[:alpha] = Sass::Util.check_range("Alpha channel", 0..1, @attrs[:alpha])
119
114
  end
120
115
 
121
116
  # The red component of the color.
@@ -366,11 +366,10 @@ module Sass::Script
366
366
  Color.new([red, green, blue].map do |c|
367
367
  v = c.value
368
368
  if c.numerator_units == ["%"] && c.denominator_units.empty?
369
- next v * 255 / 100.0 if (0..100).include?(v)
370
- raise ArgumentError.new("Color value #{c} must be between 0% and 100% inclusive")
369
+ v = Sass::Util.check_range("Color value", 0..100, c, '%')
370
+ v * 255 / 100.0
371
371
  else
372
- next v if (0..255).include?(v)
373
- raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive")
372
+ Sass::Util.check_range("Color value", 0..255, c)
374
373
  end
375
374
  end)
376
375
  end
@@ -410,10 +409,7 @@ module Sass::Script
410
409
  assert_type color, :Color
411
410
  assert_type alpha, :Number
412
411
 
413
- unless (0..1).include?(alpha.value)
414
- raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1 inclusive")
415
- end
416
-
412
+ Sass::Util.check_range('Alpha channel', 0..1, alpha)
417
413
  color.with(:alpha => alpha.value)
418
414
  when 4
419
415
  red, green, blue, alpha = args
@@ -463,16 +459,11 @@ module Sass::Script
463
459
  assert_type lightness, :Number
464
460
  assert_type alpha, :Number
465
461
 
466
- unless (0..1).include?(alpha.value)
467
- raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1")
468
- end
462
+ Sass::Util.check_range('Alpha channel', 0..1, alpha)
469
463
 
470
- original_s = saturation
471
- original_l = lightness
472
- # This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
473
- h, s, l = [hue, saturation, lightness].map { |a| a.value }
474
- raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") unless (0..100).include?(s)
475
- raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") unless (0..100).include?(l)
464
+ h = hue.value
465
+ s = Sass::Util.check_range('Saturation', 0..100, saturation, '%')
466
+ l = Sass::Util.check_range('Lightness', 0..100, lightness, '%')
476
467
 
477
468
  Color.new(:hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
478
469
  end
@@ -778,9 +769,7 @@ module Sass::Script
778
769
 
779
770
  next unless val = kwargs.delete(name)
780
771
  assert_type val, :Number, name
781
- if range && !range.include?(val.value)
782
- raise ArgumentError.new("$#{name}: Amount #{val} must be between #{range.first}#{units} and #{range.last}#{units}")
783
- end
772
+ Sass::Util.check_range("$#{name}: Amount", range, val, units) if range
784
773
  adjusted = color.send(name) + val.value
785
774
  adjusted = [0, Sass::Util.restrict(adjusted, range)].max if range
786
775
  [name.to_sym, adjusted]
@@ -849,8 +838,8 @@ module Sass::Script
849
838
  assert_type val, :Number, name
850
839
  if !(val.numerator_units == ['%'] && val.denominator_units.empty?)
851
840
  raise ArgumentError.new("$#{name}: Amount #{val} must be a % (e.g. #{val.value}%)")
852
- elsif !(-100..100).include?(val.value)
853
- raise ArgumentError.new("$#{name}: Amount #{val} must be between -100% and 100%")
841
+ else
842
+ Sass::Util.check_range("$#{name}: Amount", -100..100, val, '%')
854
843
  end
855
844
 
856
845
  current = color.send(name)
@@ -944,9 +933,7 @@ module Sass::Script
944
933
  assert_type color2, :Color
945
934
  assert_type weight, :Number
946
935
 
947
- unless (0..100).include?(weight.value)
948
- raise ArgumentError.new("Weight #{weight} must be between 0% and 100%")
949
- end
936
+ Sass::Util.check_range("Weight", 0..100, weight, '%')
950
937
 
951
938
  # This algorithm factors in both the user-provided weight
952
939
  # and the difference between the alpha values of the two colors
@@ -1028,6 +1015,7 @@ module Sass::Script
1028
1015
  :green => (255 - color.green),
1029
1016
  :blue => (255 - color.blue))
1030
1017
  end
1018
+ declare :invert, [:color]
1031
1019
 
1032
1020
  # Removes quotes from a string if the string is quoted,
1033
1021
  # or returns the same string if it's not.
@@ -1381,9 +1369,7 @@ module Sass::Script
1381
1369
  def _adjust(color, amount, attr, range, op, units = "")
1382
1370
  assert_type color, :Color
1383
1371
  assert_type amount, :Number
1384
- unless range.include?(amount.value)
1385
- raise ArgumentError.new("Amount #{amount} must be between #{range.first}#{units} and #{range.last}#{units}")
1386
- end
1372
+ Sass::Util.check_range('Amount', range, amount, units)
1387
1373
 
1388
1374
  # TODO: is it worth restricting here,
1389
1375
  # or should we do so in the Color constructor itself,
@@ -669,11 +669,7 @@ MESSAGE
669
669
  tok(SUBSTRINGMATCH)
670
670
  @expected = "identifier or string"
671
671
  ss
672
- if val = tok(IDENT)
673
- val = [val]
674
- else
675
- val = expr!(:interp_string)
676
- end
672
+ val = interp_ident || expr!(:interp_string)
677
673
  ss
678
674
  end
679
675
  tok(/\]/)
@@ -132,6 +132,14 @@ module Sass
132
132
  Sass::Tree::Visitors::ToCss.visit(self)
133
133
  end
134
134
 
135
+ # Returns a representation of the node for debugging purposes.
136
+ #
137
+ # @return [String]
138
+ def inspect
139
+ return self.class.to_s unless has_children
140
+ "(#{self.class} #{children.map {|c| c.inspect}.join(' ')})"
141
+ end
142
+
135
143
  # Converts a static CSS tree (e.g. the output of \{Tree::Visitors::Cssize})
136
144
  # into another static CSS tree,
137
145
  # with the given extensions applied to all relevant {RuleNode}s.
@@ -138,6 +138,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
138
138
  elsif node.expr; "else if"
139
139
  else; "else"
140
140
  end
141
+ @is_else = false
141
142
  str = "#{tab_str}@#{name}"
142
143
  str << " #{node.expr.to_sass(@options)}" if node.expr
143
144
  str << yield
@@ -233,6 +233,26 @@ module Sass
233
233
  return hash.sort_by {|k, v| k}
234
234
  end
235
235
 
236
+ # Asserts that `value` falls within `range` (inclusive), leaving
237
+ # room for slight floating-point errors.
238
+ #
239
+ # @param name [String] The name of the value. Used in the error message.
240
+ # @param range [Range] The allowed range of values.
241
+ # @param value [Numeric, Sass::Script::Number] The value to check.
242
+ # @param unit [String] The unit of the value. Used in error reporting.
243
+ # @return [Numeric] `value` adjusted to fall within range, if it
244
+ # was outside by a floating-point margin.
245
+ def check_range(name, range, value, unit='')
246
+ grace = (-0.00001..0.00001)
247
+ str = value.to_s
248
+ value = value.value if value.is_a?(Sass::Script::Number)
249
+ return value if range.include?(value)
250
+ return range.first if grace.include?(value - range.first)
251
+ return range.last if grace.include?(value - range.last)
252
+ raise ArgumentError.new(
253
+ "#{name} #{str} must be between #{range.first}#{unit} and #{range.last}#{unit}")
254
+ end
255
+
236
256
  # Returns information about the caller of the previous method.
237
257
  #
238
258
  # @param entry [String] An entry in the `#caller` list, or a similarly formatted string
@@ -399,6 +419,14 @@ module Sass
399
419
  RUBY_ENGINE == "ironruby"
400
420
  end
401
421
 
422
+ # Like `Dir.glob`, but works with backslash-separated paths on Windows.
423
+ #
424
+ # @param path [String]
425
+ def glob(path)
426
+ path = path.gsub('\\', '/') if windows?
427
+ Dir.glob(path)
428
+ end
429
+
402
430
  ## Cross-Ruby-Version Compatibility
403
431
 
404
432
  # Whether or not this is running under Ruby 1.8 or lower.
@@ -1168,6 +1168,32 @@ $foo: ();
1168
1168
  SCSS
1169
1169
  end
1170
1170
 
1171
+ def test_nested_if_statements
1172
+ assert_renders(<<SASS, <<SCSS)
1173
+ @if $foo
1174
+ one
1175
+ a: b
1176
+ @else
1177
+ @if $bar
1178
+ two
1179
+ a: b
1180
+ @else
1181
+ three
1182
+ a: b
1183
+ SASS
1184
+ @if $foo {
1185
+ one {
1186
+ a: b; } }
1187
+ @else {
1188
+ @if $bar {
1189
+ two {
1190
+ a: b; } }
1191
+ @else {
1192
+ three {
1193
+ a: b; } } }
1194
+ SCSS
1195
+ end
1196
+
1171
1197
  private
1172
1198
 
1173
1199
  def assert_sass_to_sass(sass, options = {})
@@ -76,7 +76,7 @@ class SassFunctionTest < Test::Unit::TestCase
76
76
 
77
77
  def test_hsl_checks_bounds
78
78
  assert_error_message("Saturation -114 must be between 0% and 100% for `hsl'", "hsl(10, -114, 12)");
79
- assert_error_message("Lightness 256 must be between 0% and 100% for `hsl'", "hsl(10, 10, 256%)");
79
+ assert_error_message("Lightness 256% must be between 0% and 100% for `hsl'", "hsl(10, 10, 256%)");
80
80
  end
81
81
 
82
82
  def test_hsl_checks_types
@@ -94,7 +94,7 @@ class SassFunctionTest < Test::Unit::TestCase
94
94
 
95
95
  def test_hsla_checks_bounds
96
96
  assert_error_message("Saturation -114 must be between 0% and 100% for `hsla'", "hsla(10, -114, 12, 1)");
97
- assert_error_message("Lightness 256 must be between 0% and 100% for `hsla'", "hsla(10, 10, 256%, 0)");
97
+ assert_error_message("Lightness 256% must be between 0% and 100% for `hsla'", "hsla(10, 10, 256%, 0)");
98
98
  assert_error_message("Alpha channel -0.1 must be between 0 and 1 for `hsla'", "hsla(10, 10, 10, -0.1)");
99
99
  assert_error_message("Alpha channel 1.1 must be between 0 and 1 for `hsla'", "hsla(10, 10, 10, 1.1)");
100
100
  end
@@ -169,24 +169,24 @@ class SassFunctionTest < Test::Unit::TestCase
169
169
  end
170
170
 
171
171
  def test_rgb_tests_bounds
172
- assert_error_message("Color value 256 must be between 0 and 255 inclusive for `rgb'",
172
+ assert_error_message("Color value 256 must be between 0 and 255 for `rgb'",
173
173
  "rgb(256, 1, 1)")
174
- assert_error_message("Color value 256 must be between 0 and 255 inclusive for `rgb'",
174
+ assert_error_message("Color value 256 must be between 0 and 255 for `rgb'",
175
175
  "rgb(1, 256, 1)")
176
- assert_error_message("Color value 256 must be between 0 and 255 inclusive for `rgb'",
176
+ assert_error_message("Color value 256 must be between 0 and 255 for `rgb'",
177
177
  "rgb(1, 1, 256)")
178
- assert_error_message("Color value 256 must be between 0 and 255 inclusive for `rgb'",
178
+ assert_error_message("Color value 256 must be between 0 and 255 for `rgb'",
179
179
  "rgb(1, 256, 257)")
180
- assert_error_message("Color value -1 must be between 0 and 255 inclusive for `rgb'",
180
+ assert_error_message("Color value -1 must be between 0 and 255 for `rgb'",
181
181
  "rgb(-1, 1, 1)")
182
182
  end
183
183
 
184
184
  def test_rgb_test_percent_bounds
185
- assert_error_message("Color value 100.1% must be between 0% and 100% inclusive for `rgb'",
185
+ assert_error_message("Color value 100.1% must be between 0% and 100% for `rgb'",
186
186
  "rgb(100.1%, 0, 0)")
187
- assert_error_message("Color value -0.1% must be between 0% and 100% inclusive for `rgb'",
187
+ assert_error_message("Color value -0.1% must be between 0% and 100% for `rgb'",
188
188
  "rgb(0, -0.1%, 0)")
189
- assert_error_message("Color value 101% must be between 0% and 100% inclusive for `rgb'",
189
+ assert_error_message("Color value 101% must be between 0% and 100% for `rgb'",
190
190
  "rgb(0, 0, 101%)")
191
191
  end
192
192
 
@@ -204,19 +204,19 @@ class SassFunctionTest < Test::Unit::TestCase
204
204
  end
205
205
 
206
206
  def test_rgb_tests_bounds
207
- assert_error_message("Color value 256 must be between 0 and 255 inclusive for `rgba'",
207
+ assert_error_message("Color value 256 must be between 0 and 255 for `rgba'",
208
208
  "rgba(256, 1, 1, 0.3)")
209
- assert_error_message("Color value 256 must be between 0 and 255 inclusive for `rgba'",
209
+ assert_error_message("Color value 256 must be between 0 and 255 for `rgba'",
210
210
  "rgba(1, 256, 1, 0.3)")
211
- assert_error_message("Color value 256 must be between 0 and 255 inclusive for `rgba'",
211
+ assert_error_message("Color value 256 must be between 0 and 255 for `rgba'",
212
212
  "rgba(1, 1, 256, 0.3)")
213
- assert_error_message("Color value 256 must be between 0 and 255 inclusive for `rgba'",
213
+ assert_error_message("Color value 256 must be between 0 and 255 for `rgba'",
214
214
  "rgba(1, 256, 257, 0.3)")
215
- assert_error_message("Color value -1 must be between 0 and 255 inclusive for `rgba'",
215
+ assert_error_message("Color value -1 must be between 0 and 255 for `rgba'",
216
216
  "rgba(-1, 1, 1, 0.3)")
217
- assert_error_message("Alpha channel -0.2 must be between 0 and 1 inclusive for `rgba'",
217
+ assert_error_message("Alpha channel -0.2 must be between 0 and 1 for `rgba'",
218
218
  "rgba(1, 1, 1, -0.2)")
219
- assert_error_message("Alpha channel 1.2 must be between 0 and 1 inclusive for `rgba'",
219
+ assert_error_message("Alpha channel 1.2 must be between 0 and 1 for `rgba'",
220
220
  "rgba(1, 1, 1, 1.2)")
221
221
  end
222
222
 
@@ -713,15 +713,15 @@ class SassFunctionTest < Test::Unit::TestCase
713
713
 
714
714
  def test_change_color_argument_errors
715
715
  # Range
716
- assert_error_message("Saturation must be between 0 and 100 for `change-color'",
716
+ assert_error_message("Saturation 101% must be between 0% and 100% for `change-color'",
717
717
  "change-color(blue, $saturation: 101%)")
718
- assert_error_message("Lightness must be between 0 and 100 for `change-color'",
718
+ assert_error_message("Lightness 101% must be between 0% and 100% for `change-color'",
719
719
  "change-color(blue, $lightness: 101%)")
720
- assert_error_message("Red value must be between 0 and 255 for `change-color'",
720
+ assert_error_message("Red value -1 must be between 0 and 255 for `change-color'",
721
721
  "change-color(blue, $red: -1)")
722
- assert_error_message("Green value must be between 0 and 255 for `change-color'",
722
+ assert_error_message("Green value 256 must be between 0 and 255 for `change-color'",
723
723
  "change-color(blue, $green: 256)")
724
- assert_error_message("Blue value must be between 0 and 255 for `change-color'",
724
+ assert_error_message("Blue value 500 must be between 0 and 255 for `change-color'",
725
725
  "change-color(blue, $blue: 500)")
726
726
 
727
727
  # Unknown argument
@@ -1021,6 +1021,12 @@ MSG
1021
1021
  assert_equal "only-kw-args(a, b, c)", evaluate("only-kw-args($a: 1, $b: 2, $c: 3)")
1022
1022
  end
1023
1023
 
1024
+ ## Regression Tests
1025
+
1026
+ def test_saturation_bounds
1027
+ assert_equal "#fbfdff", evaluate("hsl(hue(#fbfdff), saturation(#fbfdff), lightness(#fbfdff))")
1028
+ end
1029
+
1024
1030
  private
1025
1031
 
1026
1032
  def evaluate(value)