sass 3.2.0.alpha.104 → 3.2.0.alpha.236

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.
Files changed (49) hide show
  1. data/REVISION +1 -1
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/lib/sass/engine.rb +8 -1
  5. data/lib/sass/exec.rb +2 -2
  6. data/lib/sass/plugin/compiler.rb +51 -43
  7. data/lib/sass/script/color.rb +4 -9
  8. data/lib/sass/script/funcall.rb +11 -2
  9. data/lib/sass/script/functions.rb +16 -30
  10. data/lib/sass/script/lexer.rb +7 -1
  11. data/lib/sass/script/list.rb +2 -2
  12. data/lib/sass/script/literal.rb +8 -0
  13. data/lib/sass/script/null.rb +34 -0
  14. data/lib/sass/script/operation.rb +4 -0
  15. data/lib/sass/script/parser.rb +4 -2
  16. data/lib/sass/scss/parser.rb +1 -5
  17. data/lib/sass/scss/rx.rb +1 -1
  18. data/lib/sass/tree/directive_node.rb +5 -0
  19. data/lib/sass/tree/media_node.rb +3 -0
  20. data/lib/sass/tree/prop_node.rb +7 -3
  21. data/lib/sass/tree/visitors/base.rb +1 -1
  22. data/lib/sass/tree/visitors/check_nesting.rb +21 -18
  23. data/lib/sass/tree/visitors/convert.rb +1 -0
  24. data/lib/sass/tree/visitors/perform.rb +3 -2
  25. data/lib/sass/tree/visitors/to_css.rb +1 -0
  26. data/lib/sass/util.rb +28 -0
  27. data/test/sass/conversion_test.rb +33 -0
  28. data/test/sass/engine_test.rb +118 -3
  29. data/test/sass/functions_test.rb +56 -24
  30. data/test/sass/script_test.rb +60 -4
  31. data/test/sass/scss/scss_test.rb +10 -0
  32. data/vendor/listen/CHANGELOG.md +19 -1
  33. data/vendor/listen/README.md +27 -12
  34. data/vendor/listen/lib/listen/adapter.rb +9 -1
  35. data/vendor/listen/lib/listen/adapters/linux.rb +18 -7
  36. data/vendor/listen/lib/listen/adapters/windows.rb +7 -8
  37. data/vendor/listen/lib/listen/directory_record.rb +108 -48
  38. data/vendor/listen/lib/listen/listener.rb +28 -11
  39. data/vendor/listen/lib/listen/multi_listener.rb +6 -6
  40. data/vendor/listen/lib/listen/version.rb +1 -1
  41. data/vendor/listen/spec/listen/adapters/linux_spec.rb +11 -0
  42. data/vendor/listen/spec/listen/directory_record_spec.rb +268 -41
  43. data/vendor/listen/spec/listen/listener_spec.rb +8 -4
  44. data/vendor/listen/spec/listen/multi_listener_spec.rb +9 -4
  45. data/vendor/listen/spec/support/adapter_helper.rb +178 -0
  46. data/vendor/listen/spec/support/directory_record_helper.rb +24 -4
  47. data/vendor/listen/spec/support/listeners_helper.rb +11 -0
  48. metadata +11 -11
  49. data/lib/sass/plugin/listener.rb +0 -61
data/REVISION CHANGED
@@ -1 +1 @@
1
- 50209c8c1a8488b0180e42d5fbb63d1ba0297d54
1
+ 4f1b0c5be641db7ccb1d6cd7e50d1b8c809f3b4d
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
@@ -147,7 +147,7 @@ def get_version
147
147
  version.map! {|n| n =~ /^[0-9]+$/ ? n.to_i : n}
148
148
  return written_version unless version.size == 5 && version[3] == "alpha" # prerelease
149
149
 
150
- return written_version if (commit_count = `git log --pretty=oneline --first-parent stable.. | wc -l`).empty?
150
+ return written_version if (commit_count = `git log --pretty=oneline HEAD ^stable | wc -l`).empty?
151
151
  version[4] = commit_count.strip
152
152
  version.join('.')
153
153
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.0.alpha.104
1
+ 3.2.0.alpha.236
@@ -600,7 +600,14 @@ WARNING
600
600
  else
601
601
  expr = parse_script(value, :offset => line.offset + line.text.index(value))
602
602
  end
603
- Tree::PropNode.new(parse_interp(name), expr, prop)
603
+ node = Tree::PropNode.new(parse_interp(name), expr, prop)
604
+ if value.strip.empty? && line.children.empty?
605
+ raise SyntaxError.new(
606
+ "Invalid property: \"#{node.declaration}\" (no value)." +
607
+ node.pseudo_class_selector_message)
608
+ end
609
+
610
+ node
604
611
  end
605
612
 
606
613
  def parse_variable(line)
@@ -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
 
@@ -595,7 +595,7 @@ END
595
595
  end
596
596
 
597
597
  ext = @options[:from]
598
- Dir.glob("#{@options[:input]}/**/*.#{ext}") do |f|
598
+ Sass::Util.glob("#{@options[:input]}/**/*.#{ext}") do |f|
599
599
  output =
600
600
  if @options[:in_place]
601
601
  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
 
@@ -232,21 +232,16 @@ module Sass::Script
232
232
  [:red, :green, :blue].each do |k|
233
233
  next if @attrs[k].nil?
234
234
  @attrs[k] = @attrs[k].to_i
235
- next if (0..255).include?(@attrs[k])
236
- raise ArgumentError.new("#{k.to_s.capitalize} value must be between 0 and 255")
235
+ Sass::Util.check_range("#{k.to_s.capitalize} value", 0..255, @attrs[k])
237
236
  end
238
237
 
239
238
  [:saturation, :lightness].each do |k|
240
239
  next if @attrs[k].nil?
241
- @attrs[k] = 0 if @attrs[k] < 0.00001 && @attrs[k] > -0.00001
242
- @attrs[k] = 100 if @attrs[k] - 100 < 0.00001 && @attrs[k] - 100 > -0.00001
243
- next if (0..100).include?(@attrs[k])
244
- raise ArgumentError.new("#{k.to_s.capitalize} must be between 0 and 100")
240
+ value = Number.new(@attrs[k], ['%']) # Get correct unit for error messages
241
+ @attrs[k] = Sass::Util.check_range("#{k.to_s.capitalize}", 0..100, value, '%')
245
242
  end
246
243
 
247
- unless (0..1).include?(@attrs[:alpha])
248
- raise ArgumentError.new("Alpha channel must be between 0 and 1")
249
- end
244
+ @attrs[:alpha] = Sass::Util.check_range("Alpha channel", 0..1, @attrs[:alpha])
250
245
  end
251
246
 
252
247
  # The red component of the color.
@@ -136,7 +136,12 @@ module Sass
136
136
  if signature.var_kwargs
137
137
  args << keywords
138
138
  else
139
- raise Sass::SyntaxError.new("Function #{name} doesn't take an argument named $#{keywords.keys.sort.first}")
139
+ argname = keywords.keys.sort.first
140
+ if signature.args.include?(argname)
141
+ raise Sass::SyntaxError.new("Function #{name} was passed argument $#{argname} both by position and by name")
142
+ else
143
+ raise Sass::SyntaxError.new("Function #{name} doesn't have an argument named $#{argname}")
144
+ end
140
145
  end
141
146
  end
142
147
 
@@ -158,9 +163,13 @@ module Sass
158
163
 
159
164
  environment = function.args.zip(args).
160
165
  inject(Sass::Environment.new(function.environment)) do |env, ((var, default), value)|
166
+ if value && keywords.include?(var.underscored_name)
167
+ raise Sass::SyntaxError.new("Function #{@name} was passed argument $#{var.name} both by position and by name")
168
+ end
169
+
161
170
  env.set_local_var(var.name,
162
171
  value || keywords[var.underscored_name] || (default && default.perform(env)))
163
- raise Sass::SyntaxError.new("Function #{@name} is missing parameter #{var.inspect}.") unless env.var(var.name)
172
+ raise Sass::SyntaxError.new("Function #{@name} is missing argument #{var.inspect}") unless env.var(var.name)
164
173
  env
165
174
  end
166
175
 
@@ -375,11 +375,10 @@ module Sass::Script
375
375
  Color.new([red, green, blue].map do |c|
376
376
  v = c.value
377
377
  if c.numerator_units == ["%"] && c.denominator_units.empty?
378
- next v * 255 / 100.0 if (0..100).include?(v)
379
- raise ArgumentError.new("Color value #{c} must be between 0% and 100% inclusive")
378
+ v = Sass::Util.check_range("Color value", 0..100, c, '%')
379
+ v * 255 / 100.0
380
380
  else
381
- next v if (0..255).include?(v)
382
- raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive")
381
+ Sass::Util.check_range("Color value", 0..255, c)
383
382
  end
384
383
  end)
385
384
  end
@@ -419,10 +418,7 @@ module Sass::Script
419
418
  assert_type color, :Color
420
419
  assert_type alpha, :Number
421
420
 
422
- unless (0..1).include?(alpha.value)
423
- raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1 inclusive")
424
- end
425
-
421
+ Sass::Util.check_range('Alpha channel', 0..1, alpha)
426
422
  color.with(:alpha => alpha.value)
427
423
  when 4
428
424
  red, green, blue, alpha = args
@@ -472,16 +468,11 @@ module Sass::Script
472
468
  assert_type lightness, :Number
473
469
  assert_type alpha, :Number
474
470
 
475
- unless (0..1).include?(alpha.value)
476
- raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1")
477
- end
471
+ Sass::Util.check_range('Alpha channel', 0..1, alpha)
478
472
 
479
- original_s = saturation
480
- original_l = lightness
481
- # This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
482
- h, s, l = [hue, saturation, lightness].map { |a| a.value }
483
- raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") unless (0..100).include?(s)
484
- raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") unless (0..100).include?(l)
473
+ h = hue.value
474
+ s = Sass::Util.check_range('Saturation', 0..100, saturation, '%')
475
+ l = Sass::Util.check_range('Lightness', 0..100, lightness, '%')
485
476
 
486
477
  Color.new(:hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
487
478
  end
@@ -804,9 +795,7 @@ module Sass::Script
804
795
 
805
796
  next unless val = kwargs.delete(name)
806
797
  assert_type val, :Number, name
807
- if range && !range.include?(val.value)
808
- raise ArgumentError.new("$#{name}: Amount #{val} must be between #{range.first}#{units} and #{range.last}#{units}")
809
- end
798
+ Sass::Util.check_range("$#{name}: Amount", range, val, units) if range
810
799
  adjusted = color.send(name) + val.value
811
800
  adjusted = [0, Sass::Util.restrict(adjusted, range)].max if range
812
801
  [name.to_sym, adjusted]
@@ -875,8 +864,8 @@ module Sass::Script
875
864
  assert_type val, :Number, name
876
865
  if !(val.numerator_units == ['%'] && val.denominator_units.empty?)
877
866
  raise ArgumentError.new("$#{name}: Amount #{val} must be a % (e.g. #{val.value}%)")
878
- elsif !(-100..100).include?(val.value)
879
- raise ArgumentError.new("$#{name}: Amount #{val} must be between -100% and 100%")
867
+ else
868
+ Sass::Util.check_range("$#{name}: Amount", -100..100, val, '%')
880
869
  end
881
870
 
882
871
  current = color.send(name)
@@ -970,9 +959,7 @@ module Sass::Script
970
959
  assert_type color2, :Color
971
960
  assert_type weight, :Number
972
961
 
973
- unless (0..100).include?(weight.value)
974
- raise ArgumentError.new("Weight #{weight} must be between 0% and 100%")
975
- end
962
+ Sass::Util.check_range("Weight", 0..100, weight, '%')
976
963
 
977
964
  # This algorithm factors in both the user-provided weight
978
965
  # and the difference between the alpha values of the two colors
@@ -1054,6 +1041,7 @@ module Sass::Script
1054
1041
  :green => (255 - color.green),
1055
1042
  :blue => (255 - color.blue))
1056
1043
  end
1044
+ declare :invert, [:color]
1057
1045
 
1058
1046
  # Removes quotes from a string if the string is quoted,
1059
1047
  # or returns the same string if it's not.
@@ -1186,8 +1174,8 @@ module Sass::Script
1186
1174
  # Rounds a number up to the nearest whole number.
1187
1175
  #
1188
1176
  # @example
1189
- # ciel(10.4px) => 11px
1190
- # ciel(10.6px) => 11px
1177
+ # ceil(10.4px) => 11px
1178
+ # ceil(10.6px) => 11px
1191
1179
  # @param value [Number] The number
1192
1180
  # @return [Number] The rounded number
1193
1181
  # @raise [ArgumentError] if `value` isn't a number
@@ -1438,9 +1426,7 @@ module Sass::Script
1438
1426
  def _adjust(color, amount, attr, range, op, units = "")
1439
1427
  assert_type color, :Color
1440
1428
  assert_type amount, :Number
1441
- unless range.include?(amount.value)
1442
- raise ArgumentError.new("Amount #{amount} must be between #{range.first}#{units} and #{range.last}#{units}")
1443
- end
1429
+ Sass::Util.check_range('Amount', range, amount, units)
1444
1430
 
1445
1431
  # TODO: is it worth restricting here,
1446
1432
  # or should we do so in the Color constructor itself,
@@ -90,6 +90,7 @@ module Sass
90
90
  :number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
91
91
  :color => HEXCOLOR,
92
92
  :bool => /(true|false)\b/,
93
+ :null => /null\b/,
93
94
  :ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")})})},
94
95
  :op => %r{(#{Regexp.union(*OP_NAMES)})},
95
96
  }
@@ -234,7 +235,7 @@ module Sass
234
235
  end
235
236
 
236
237
  variable || string(:double, false) || string(:single, false) || number ||
237
- color || bool || string(:uri, false) || raw(UNICODERANGE) ||
238
+ color || bool || null || string(:uri, false) || raw(UNICODERANGE) ||
238
239
  special_fun || special_val || ident_op || ident || op
239
240
  end
240
241
 
@@ -292,6 +293,11 @@ MESSAGE
292
293
  [:bool, Script::Bool.new(s == 'true')]
293
294
  end
294
295
 
296
+ def null
297
+ return unless scan(REGULAR_EXPRESSIONS[:null])
298
+ [:null, Script::Null.new]
299
+ end
300
+
295
301
  def special_fun
296
302
  return unless str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
297
303
  str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
@@ -41,14 +41,14 @@ module Sass::Script
41
41
  # @see Node#to_s
42
42
  def to_s(opts = {})
43
43
  raise Sass::SyntaxError.new("() isn't a valid CSS value.") if value.empty?
44
- return value.reject {|e| e.is_a?(List) && e.value.empty?}.map {|e| e.to_s(opts)}.join(sep_str)
44
+ return value.reject {|e| e.is_a?(Null) || e.is_a?(List) && e.value.empty?}.map {|e| e.to_s(opts)}.join(sep_str)
45
45
  end
46
46
 
47
47
  # @see Node#to_sass
48
48
  def to_sass(opts = {})
49
49
  return "()" if value.empty?
50
50
  precedence = Sass::Script::Parser.precedence_of(separator)
51
- value.map do |v|
51
+ value.reject {|e| e.is_a?(Null)}.map do |v|
52
52
  if v.is_a?(List) && Sass::Script::Parser.precedence_of(v.separator) <= precedence
53
53
  "(#{v.to_sass(opts)})"
54
54
  else
@@ -9,6 +9,7 @@ module Sass::Script
9
9
  require 'sass/script/number'
10
10
  require 'sass/script/color'
11
11
  require 'sass/script/bool'
12
+ require 'sass/script/null'
12
13
  require 'sass/script/list'
13
14
 
14
15
  # Returns the Ruby value of the literal.
@@ -217,6 +218,13 @@ MSG
217
218
  end
218
219
  alias_method :to_sass, :to_s
219
220
 
221
+ # Returns whether or not this object is null.
222
+ #
223
+ # @return [Boolean] `false`
224
+ def null?
225
+ false
226
+ end
227
+
220
228
  protected
221
229
 
222
230
  # Evaluates the literal.
@@ -0,0 +1,34 @@
1
+ require 'sass/script/literal'
2
+
3
+ module Sass::Script
4
+ # A SassScript object representing a null value.
5
+ class Null < Literal
6
+ # Creates a new null literal.
7
+ def initialize
8
+ super nil
9
+ end
10
+
11
+ # @return [Boolean] `false` (the Ruby boolean value)
12
+ def to_bool
13
+ false
14
+ end
15
+
16
+ # @return [Boolean] `true`
17
+ def null?
18
+ true
19
+ end
20
+
21
+ # @return [String] '' (An empty string)
22
+ def to_s(opts = {})
23
+ ''
24
+ end
25
+ alias_method :to_sass, :to_s
26
+
27
+ # Returns a string representing a null value.
28
+ #
29
+ # @return [String]
30
+ def inspect
31
+ 'null'
32
+ end
33
+ end
34
+ end
@@ -82,6 +82,10 @@ module Sass::Script
82
82
 
83
83
  literal2 = @operand2.perform(environment)
84
84
 
85
+ if literal1.is_a?(Null) || literal2.is_a?(Null)
86
+ raise Sass::SyntaxError.new("Invalid null operation: \"#{literal1.inspect} #{@operator} #{literal2.inspect}\".")
87
+ end
88
+
85
89
  begin
86
90
  opts(literal1.send(@operator, literal2))
87
91
  rescue NoMethodError => e