haml 3.1.0.alpha.19 → 3.1.0.alpha.22

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (33) hide show
  1. data/EDGE_GEM_VERSION +1 -1
  2. data/VERSION +1 -1
  3. data/lib/haml/precompiler.rb +1 -0
  4. data/lib/haml/template/plugin.rb +16 -6
  5. data/vendor/sass/doc-src/SASS_CHANGELOG.md +41 -0
  6. data/vendor/sass/doc-src/SASS_REFERENCE.md +37 -6
  7. data/vendor/sass/lib/sass.rb +7 -3
  8. data/vendor/sass/lib/sass/engine.rb +4 -4
  9. data/vendor/sass/lib/sass/environment.rb +24 -15
  10. data/vendor/sass/lib/sass/less.rb +31 -12
  11. data/vendor/sass/lib/sass/plugin/staleness_checker.rb +1 -1
  12. data/vendor/sass/lib/sass/script/funcall.rb +51 -9
  13. data/vendor/sass/lib/sass/script/functions.rb +189 -57
  14. data/vendor/sass/lib/sass/script/node.rb +7 -1
  15. data/vendor/sass/lib/sass/script/number.rb +21 -15
  16. data/vendor/sass/lib/sass/script/operation.rb +10 -5
  17. data/vendor/sass/lib/sass/script/parser.rb +61 -17
  18. data/vendor/sass/lib/sass/script/string.rb +2 -3
  19. data/vendor/sass/lib/sass/script/variable.rb +6 -0
  20. data/vendor/sass/lib/sass/scss/parser.rb +8 -5
  21. data/vendor/sass/lib/sass/selector/sequence.rb +2 -2
  22. data/vendor/sass/lib/sass/tree/mixin_node.rb +25 -5
  23. data/vendor/sass/lib/sass/tree/node.rb +2 -2
  24. data/vendor/sass/lib/sass/tree/prop_node.rb +9 -6
  25. data/vendor/sass/lib/sass/tree/rule_node.rb +9 -8
  26. data/vendor/sass/lib/sass/util.rb +5 -3
  27. data/vendor/sass/test/sass/conversion_test.rb +14 -0
  28. data/vendor/sass/test/sass/engine_test.rb +85 -0
  29. data/vendor/sass/test/sass/functions_test.rb +89 -0
  30. data/vendor/sass/test/sass/less_conversion_test.rb +24 -3
  31. data/vendor/sass/test/sass/script_conversion_test.rb +65 -0
  32. data/vendor/sass/test/sass/scss/scss_test.rb +63 -0
  33. metadata +2 -2
@@ -32,7 +32,13 @@ module Sass::Script
32
32
  # @param options [{Symbol => Object}] The options
33
33
  def options=(options)
34
34
  @options = options
35
- children.each {|c| c.options = options}
35
+ children.each do |c|
36
+ if c.is_a? Hash
37
+ c.values.each {|v| v.options = options }
38
+ else
39
+ c.options = options
40
+ end
41
+ end
36
42
  end
37
43
 
38
44
  # Sets the context for this node,
@@ -41,10 +41,13 @@ module Sass::Script
41
41
  # @api public
42
42
  PRECISION = 1000.0
43
43
 
44
+ # Used so we don't allocate two new arrays for each new number.
45
+ NO_UNITS = []
46
+
44
47
  # @param value [Numeric] The value of the number
45
48
  # @param numerator_units [Array<String>] See \{#numerator\_units}
46
49
  # @param denominator_units [Array<String>] See \{#denominator\_units}
47
- def initialize(value, numerator_units = [], denominator_units = [])
50
+ def initialize(value, numerator_units = NO_UNITS, denominator_units = NO_UNITS)
48
51
  super(value)
49
52
  @numerator_units = numerator_units
50
53
  @denominator_units = denominator_units
@@ -107,7 +110,7 @@ module Sass::Script
107
110
  #
108
111
  # @return [Number] The negative value of this number
109
112
  def unary_minus
110
- Number.new(-value, numerator_units, denominator_units)
113
+ Number.new(-value, @numerator_units, @denominator_units)
111
114
  end
112
115
 
113
116
  # The SassScript `*` operation.
@@ -183,7 +186,7 @@ module Sass::Script
183
186
  if unitless?
184
187
  this = this.coerce(other.numerator_units, other.denominator_units)
185
188
  else
186
- other = other.coerce(numerator_units, denominator_units)
189
+ other = other.coerce(@numerator_units, @denominator_units)
187
190
  end
188
191
  rescue Sass::UnitConversionError
189
192
  return Sass::Script::Bool.new(false)
@@ -248,7 +251,8 @@ module Sass::Script
248
251
  #
249
252
  # @return [String] The representation
250
253
  def inspect(opts = {})
251
- "#{self.class.round(self.value)}#{unit_str}"
254
+ value = self.class.round(self.value)
255
+ unitless? ? value.to_s : "#{value}#{unit_str}"
252
256
  end
253
257
  alias_method :to_sass, :inspect
254
258
 
@@ -266,13 +270,13 @@ module Sass::Script
266
270
 
267
271
  # @return [Boolean] Whether or not this number has no units.
268
272
  def unitless?
269
- numerator_units.empty? && denominator_units.empty?
273
+ @numerator_units.empty? && @denominator_units.empty?
270
274
  end
271
275
 
272
276
  # @return [Boolean] Whether or not this number has units that can be represented in CSS
273
277
  # (that is, zero or one \{#numerator\_units}).
274
278
  def legal_units?
275
- (numerator_units.empty? || numerator_units.size == 1) && denominator_units.empty?
279
+ (@numerator_units.empty? || @numerator_units.size == 1) && @denominator_units.empty?
276
280
  end
277
281
 
278
282
  # Returns this number converted to other units.
@@ -295,8 +299,8 @@ module Sass::Script
295
299
  Number.new(if unitless?
296
300
  self.value
297
301
  else
298
- self.value * coercion_factor(self.numerator_units, num_units) /
299
- coercion_factor(self.denominator_units, den_units)
302
+ self.value * coercion_factor(@numerator_units, num_units) /
303
+ coercion_factor(@denominator_units, den_units)
300
304
  end, num_units, den_units)
301
305
  end
302
306
 
@@ -316,10 +320,10 @@ module Sass::Script
316
320
  # numerator_unit1 * numerator_unit2 / denominator_unit1 * denominator_unit2
317
321
  # @return [String] a string that represents the units in this number
318
322
  def unit_str
319
- rv = numerator_units.sort.join("*")
320
- if denominator_units.any?
323
+ rv = @numerator_units.sort.join("*")
324
+ if @denominator_units.any?
321
325
  rv << "/"
322
- rv << denominator_units.sort.join("*")
326
+ rv << @denominator_units.sort.join("*")
323
327
  end
324
328
  rv
325
329
  end
@@ -337,13 +341,15 @@ module Sass::Script
337
341
  end
338
342
  end
339
343
 
344
+ OPERATIONS = [:+, :-, :<=, :<, :>, :>=]
345
+
340
346
  def operate(other, operation)
341
347
  this = self
342
- if [:+, :-, :<=, :<, :>, :>=].include?(operation)
348
+ if OPERATIONS.include?(operation)
343
349
  if unitless?
344
350
  this = this.coerce(other.numerator_units, other.denominator_units)
345
351
  else
346
- other = other.coerce(numerator_units, denominator_units)
352
+ other = other.coerce(@numerator_units, @denominator_units)
347
353
  end
348
354
  end
349
355
  # avoid integer division
@@ -381,7 +387,7 @@ module Sass::Script
381
387
 
382
388
  def normalize!
383
389
  return if unitless?
384
- @numerator_units, @denominator_units = sans_common_units(numerator_units, denominator_units)
390
+ @numerator_units, @denominator_units = sans_common_units(@numerator_units, @denominator_units)
385
391
 
386
392
  @denominator_units.each_with_index do |d, i|
387
393
  if convertable?(d) && (u = @numerator_units.detect(&method(:convertable?)))
@@ -407,7 +413,7 @@ module Sass::Script
407
413
  end
408
414
 
409
415
  def convertable?(units)
410
- Array(units).all?(&CONVERTABLE_UNITS.method(:include?))
416
+ Array(units).all? {|u| CONVERTABLE_UNITS.include?(u)}
411
417
  end
412
418
 
413
419
  def sans_common_units(units1, units2)
@@ -36,8 +36,8 @@ module Sass::Script
36
36
  # @see Node#to_sass
37
37
  def to_sass(opts = {})
38
38
  pred = Sass::Script::Parser.precedence_of(@operator)
39
- o1 = operand_to_sass pred, @operand1, opts
40
- o2 = operand_to_sass pred, @operand2, opts
39
+ o1 = operand_to_sass @operand1, :left, opts
40
+ o2 = operand_to_sass @operand2, :right, opts
41
41
  sep =
42
42
  case @operator
43
43
  when :comma; ", "
@@ -81,9 +81,14 @@ module Sass::Script
81
81
 
82
82
  private
83
83
 
84
- def operand_to_sass(pred, op, opts = {})
85
- return "(#{op.to_sass(opts)})" if op.is_a?(Operation) &&
86
- Sass::Script::Parser.precedence_of(op.operator) < pred
84
+ def operand_to_sass(op, side, opts)
85
+ return op.to_sass(opts) unless op.is_a?(Operation)
86
+
87
+ pred = Sass::Script::Parser.precedence_of(@operator)
88
+ sub_pred = Sass::Script::Parser.precedence_of(op.operator)
89
+ assoc = Sass::Script::Parser.associative?(@operator)
90
+ return "(#{op.to_sass(opts)})" if sub_pred < pred ||
91
+ (side == :right && sub_pred == pred && !assoc)
87
92
  op.to_sass(opts)
88
93
  end
89
94
  end
@@ -74,19 +74,21 @@ module Sass
74
74
 
75
75
  # Parses the argument list for a mixin include.
76
76
  #
77
- # @return [Array<Script::Node>] The root nodes of the arguments.
77
+ # @return [(Array<Script::Node>, {String => Script::Note})]
78
+ # The root nodes of the arguments.
79
+ # Keyword arguments are in a hash from names to values.
78
80
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
79
81
  def parse_mixin_include_arglist
80
- args = []
81
-
82
+ args, keywords = [], {}
82
83
  if try_tok(:lparen)
83
- args = arglist || args
84
+ args, keywords = mixin_arglist || [[], {}]
84
85
  assert_tok(:rparen)
85
86
  end
86
87
  assert_done
87
88
 
88
89
  args.each {|a| a.options = @options}
89
- args
90
+ keywords.each {|k, v| v.options = @options}
91
+ return args, keywords
90
92
  rescue Sass::SyntaxError => e
91
93
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
92
94
  raise e
@@ -128,6 +130,8 @@ module Sass
128
130
  [:times, :div, :mod],
129
131
  ]
130
132
 
133
+ ASSOCIATIVE = [:comma, :concat, :plus, :times]
134
+
131
135
  class << self
132
136
  # Returns an integer representing the precedence
133
137
  # of the given operator.
@@ -141,6 +145,13 @@ module Sass
141
145
  raise "[BUG] Unknown operator #{op}"
142
146
  end
143
147
 
148
+ # Returns whether or not the given operation is associative.
149
+ #
150
+ # @private
151
+ def associative?(op)
152
+ ASSOCIATIVE.include?(op)
153
+ end
154
+
144
155
  private
145
156
 
146
157
  # Defines a simple left-associative production.
@@ -254,9 +265,9 @@ RUBY
254
265
 
255
266
  def funcall
256
267
  return raw unless tok = try_tok(:funcall)
257
- args = fn_arglist || []
268
+ args, keywords = fn_arglist || [[], {}]
258
269
  assert_tok(:rparen)
259
- node(Script::Funcall.new(tok.value, args))
270
+ node(Script::Funcall.new(tok.value, args, keywords))
260
271
  end
261
272
 
262
273
  def defn_arglist!(must_have_default)
@@ -289,15 +300,48 @@ RUBY
289
300
  end
290
301
 
291
302
  def fn_arglist
292
- return unless e = equals
293
- return [e] unless try_tok(:comma)
294
- [e, *assert_expr(:fn_arglist)]
303
+ arglist(:fn_arglist, :equals)
304
+ end
305
+
306
+ def mixin_arglist
307
+ arglist(:mixin_arglist, :interpolation)
295
308
  end
296
309
 
297
- def arglist
298
- return unless e = interpolation
299
- return [e] unless try_tok(:comma)
300
- [e, *assert_expr(:arglist)]
310
+ def arglist(type, subexpr)
311
+ return unless e = send(subexpr)
312
+ if @lexer.peek && @lexer.peek.type == :colon
313
+ name = e
314
+ @lexer.expected!("comma") unless name.is_a?(Variable)
315
+ assert_tok(:colon)
316
+ keywords = {name.underscored_name => assert_expr(subexpr, EXPR_NAMES[type])}
317
+ end
318
+
319
+ unless try_tok(:comma)
320
+ return [], keywords if keywords
321
+ return [e], {}
322
+ end
323
+
324
+ other_args, other_keywords = assert_expr(type)
325
+ if keywords
326
+ if other_keywords[name.underscored_name]
327
+ raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
328
+ end
329
+ return other_args, keywords.merge(other_keywords)
330
+ else
331
+ return [e, *other_args], other_keywords
332
+ end
333
+ end
334
+
335
+ def keyword_arglist
336
+ return unless var = try_tok(:const)
337
+ unless try_tok(:colon)
338
+ return_tok!
339
+ return
340
+ end
341
+ name = var[1]
342
+ value = interpolation
343
+ return {name => value} unless try_tok(:comma)
344
+ {name => value}.merge(assert_expr(:keyword_arglist))
301
345
  end
302
346
 
303
347
  def raw
@@ -359,13 +403,13 @@ RUBY
359
403
  EXPR_NAMES = {
360
404
  :string => "string",
361
405
  :default => "expression (e.g. 1px, bold)",
362
- :arglist => "mixin argument",
406
+ :mixin_arglist => "mixin argument",
363
407
  :fn_arglist => "function argument",
364
408
  }
365
409
 
366
- def assert_expr(name)
410
+ def assert_expr(name, expected = nil)
367
411
  (e = send(name)) && (return e)
368
- @lexer.expected!(EXPR_NAMES[name] || EXPR_NAMES[:default])
412
+ @lexer.expected!(expected || EXPR_NAMES[name] || EXPR_NAMES[:default])
369
413
  end
370
414
 
371
415
  def assert_tok(*names)
@@ -41,9 +41,8 @@ module Sass::Script
41
41
 
42
42
  # @see Node#to_s
43
43
  def to_s(opts = {})
44
- if self.type == :identifier
45
- return %q{""} if context == :equals && self.value.size == 0
46
- return self.value.gsub("\n", " ")
44
+ if @type == :identifier
45
+ return @context == :equals && @value.empty? ? %q{""} : @value.tr("\n", " ")
47
46
  end
48
47
 
49
48
  return "\"#{value.gsub('"', "\\\"")}\"" if opts[:quote] == %q{"}
@@ -7,9 +7,15 @@ module Sass
7
7
  # @return [String]
8
8
  attr_reader :name
9
9
 
10
+ # The underscored name of the variable.
11
+ #
12
+ # @return [String]
13
+ attr_reader :underscored_name
14
+
10
15
  # @param name [String] See \{#name}
11
16
  def initialize(name)
12
17
  @name = name
18
+ @underscored_name = name.gsub(/-/,"_")
13
19
  super()
14
20
  end
15
21
 
@@ -138,9 +138,9 @@ module Sass
138
138
 
139
139
  def include_directive
140
140
  name = tok! IDENT
141
- args = sass_script(:parse_mixin_include_arglist)
141
+ args, keywords = sass_script(:parse_mixin_include_arglist)
142
142
  ss
143
- node(Sass::Tree::MixinNode.new(name, args))
143
+ node(Sass::Tree::MixinNode.new(name, args, keywords))
144
144
  end
145
145
 
146
146
  def debug_directive
@@ -821,17 +821,20 @@ MESSAGE
821
821
  :line => line)
822
822
  end
823
823
 
824
+ # Avoid allocating lots of new strings for `#tok`.
825
+ # This is important because `#tok` is called all the time.
826
+ NEWLINE = "\n"
827
+
824
828
  def tok(rx)
825
829
  res = @scanner.scan(rx)
826
830
  if res
827
- @line += res.count("\n")
831
+ @line += res.count(NEWLINE)
828
832
  @expected = nil
829
833
  if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
830
834
  @strs.each {|s| s << res}
831
835
  end
836
+ res
832
837
  end
833
-
834
- res
835
838
  end
836
839
  end
837
840
  end
@@ -48,12 +48,12 @@ module Sass
48
48
  # @raise [Sass::SyntaxError] If a parent selector is invalid
49
49
  def resolve_parent_refs(super_seq)
50
50
  members = @members
51
- members.slice!(0) if nl = (members.first == "\n")
51
+ nl = (members.first == "\n" && members.shift)
52
52
  unless members.any? do |seq_or_op|
53
53
  seq_or_op.is_a?(SimpleSequence) && seq_or_op.members.first.is_a?(Parent)
54
54
  end
55
55
  members = []
56
- members << "\n" if nl
56
+ members << nl if nl
57
57
  members << SimpleSequence.new([Parent.new])
58
58
  members += @members
59
59
  end
@@ -11,13 +11,16 @@ module Sass::Tree
11
11
  def options=(opts)
12
12
  super
13
13
  @args.each {|a| a.context = :equals} if opts[:sass2]
14
+ @keywords.each {|k, v| v.context = :equals} if opts[:sass2]
14
15
  end
15
16
 
16
17
  # @param name [String] The name of the mixin
17
18
  # @param args [Array<Script::Node>] The arguments to the mixin
18
- def initialize(name, args)
19
+ # @param keywords [{String => Script::Node}] A hash from keyword argument names to values
20
+ def initialize(name, args, keywords)
19
21
  @name = name
20
22
  @args = args
23
+ @keywords = keywords
21
24
  super()
22
25
  end
23
26
 
@@ -42,8 +45,12 @@ module Sass::Tree
42
45
 
43
46
  # @see Node#to_src
44
47
  def to_src(tabs, opts, fmt)
45
- args = '(' + @args.map {|a| a.to_sass(opts)}.join(", ") + ')' unless @args.empty?
46
- "#{' ' * tabs}#{fmt == :sass ? '+' : '@include '}#{dasherize(@name, opts)}#{args}#{semi fmt}\n"
48
+ unless @args.empty? && @keywords.empty?
49
+ args = @args.map {|a| a.to_sass(opts)}.join(", ")
50
+ keywords = @keywords.map {|k, v| "$#{dasherize(k, opts)}: #{v.to_sass(opts)}"}.join(', ')
51
+ arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
52
+ end
53
+ "#{' ' * tabs}#{fmt == :sass ? '+' : '@include '}#{dasherize(@name, opts)}#{arglist}#{semi fmt}\n"
47
54
  end
48
55
 
49
56
  # @see Node#_cssize
@@ -73,15 +80,28 @@ module Sass::Tree
73
80
  original_env.prepare_frame(:mixin => @name)
74
81
  raise Sass::SyntaxError.new("Undefined mixin '#{@name}'.") unless mixin = environment.mixin(@name)
75
82
 
76
- raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < @args.size
83
+ passed_args = @args.dup
84
+ passed_keywords = @keywords.dup
85
+
86
+ raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < passed_args.size
77
87
  Mixin #{@name} takes #{mixin.args.size} argument#{'s' if mixin.args.size != 1}
78
88
  but #{@args.size} #{@args.size == 1 ? 'was' : 'were'} passed.
79
89
  END
80
- environment = mixin.args.zip(@args).
90
+
91
+ passed_keywords.each do |name, value|
92
+ # TODO: Make this fast
93
+ unless mixin.args.find {|(var, default)| var.underscored_name == name}
94
+ raise Sass::SyntaxError.new("Mixin #{@name} doesn't have an argument named $#{name}")
95
+ end
96
+ end
97
+
98
+ environment = mixin.args.zip(passed_args).
81
99
  inject(Sass::Environment.new(mixin.environment)) do |env, ((var, default), value)|
82
100
  env.set_local_var(var.name,
83
101
  if value
84
102
  value.perform(environment)
103
+ elsif kv = passed_keywords[var.underscored_name]
104
+ kv.perform(env)
85
105
  elsif default
86
106
  val = default.perform(env)
87
107
  if default.context == :equals && val.is_a?(Sass::Script::String)
@@ -148,8 +148,8 @@ module Sass
148
148
  # @param args [Array] Passed on to \{#\_to\_s}
149
149
  # @return [String, nil] The resulting CSS
150
150
  # @see Sass::Tree
151
- def to_s(*args)
152
- _to_s(*args)
151
+ def to_s(opts = nil)
152
+ _to_s(opts)
153
153
  rescue Sass::SyntaxError => e
154
154
  e.modify_backtrace(:filename => filename, :line => line)
155
155
  raise e