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

Sign up to get free protection for your applications and to get access to all the features.
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