sass 3.2.0.alpha.278 → 3.2.0.alpha.291

Sign up to get free protection for your applications and to get access to all the features.
data/REVISION CHANGED
@@ -1 +1 @@
1
- daf44b6e5e3a25e1fadb77a4161ffe27edea74ab
1
+ b79252d738656e1cea492c2c09624ae1581681f6
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.0.alpha.278
1
+ 3.2.0.alpha.291
@@ -1 +1 @@
1
- 04 August 2012 00:02:51 GMT
1
+ 10 August 2012 23:31:56 GMT
@@ -52,11 +52,14 @@ module Sass
52
52
  # `name`: `String`
53
53
  # : The name of the mixin/function.
54
54
  #
55
- # `args`: `Array<(String, Script::Node)>`
55
+ # `args`: `Array<(Script::Node, Script::Node)>`
56
56
  # : The arguments for the mixin/function.
57
- # Each element is a tuple containing the name of the argument
57
+ # Each element is a tuple containing the variable node of the argument
58
58
  # and the parse tree for the default value of the argument.
59
59
  #
60
+ # `splat`: `Script::Node?`
61
+ # : The variable node of the splat argument for this callable, or null.
62
+ #
60
63
  # `environment`: {Sass::Environment}
61
64
  # : The environment in which the mixin/function was defined.
62
65
  # This is captured so that the mixin/function can have access
@@ -67,7 +70,10 @@ module Sass
67
70
  #
68
71
  # `has_content`: `Boolean`
69
72
  # : Whether the callable accepts a content block.
70
- Callable = Struct.new(:name, :args, :environment, :tree, :has_content)
73
+ #
74
+ # `type`: `String`
75
+ # : The user-friendly name of the type of the callable.
76
+ Callable = Struct.new(:name, :args, :splat, :environment, :tree, :has_content, :type)
71
77
 
72
78
  # This class handles the parsing and compilation of the Sass template.
73
79
  # Example usage:
@@ -827,9 +833,9 @@ WARNING
827
833
  raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
828
834
 
829
835
  offset = line.offset + line.text.size - arg_string.size
830
- args = Script::Parser.new(arg_string.strip, @line, offset, @options).
836
+ args, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
831
837
  parse_mixin_definition_arglist
832
- Tree::MixinDefNode.new(name, args)
838
+ Tree::MixinDefNode.new(name, args, splat)
833
839
  end
834
840
 
835
841
  CONTENT_RE = /^@content\s*(.+)?$/
@@ -847,9 +853,9 @@ WARNING
847
853
  raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
848
854
 
849
855
  offset = line.offset + line.text.size - arg_string.size
850
- args, keywords = Script::Parser.new(arg_string.strip, @line, offset, @options).
856
+ args, keywords, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
851
857
  parse_mixin_include_arglist
852
- Tree::MixinNode.new(name, args, keywords)
858
+ Tree::MixinNode.new(name, args, keywords, splat)
853
859
  end
854
860
 
855
861
  FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
@@ -858,9 +864,9 @@ WARNING
858
864
  raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
859
865
 
860
866
  offset = line.offset + line.text.size - arg_string.size
861
- args = Script::Parser.new(arg_string.strip, @line, offset, @options).
867
+ args, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
862
868
  parse_function_definition_arglist
863
- Tree::FunctionNode.new(name, args)
869
+ Tree::FunctionNode.new(name, args, splat)
864
870
  end
865
871
 
866
872
  def parse_script(script, options = {})
@@ -0,0 +1,52 @@
1
+ module Sass::Script
2
+ # A SassScript object representing a variable argument list. This works just
3
+ # like a normal list, but can also contain keyword arguments.
4
+ #
5
+ # The keyword arguments attached to this list are unused except when this is
6
+ # passed as a glob argument to a function or mixin.
7
+ class ArgList < List
8
+ # Whether \{#keywords} has been accessed. If so, we assume that all keywords
9
+ # were valid for the function that created this ArgList.
10
+ #
11
+ # @return [Boolean]
12
+ attr_accessor :keywords_accessed
13
+
14
+ # Creates a new argument list.
15
+ #
16
+ # @param value [Array<Literal>] See \{List#value}.
17
+ # @param keywords [Hash<String, Literal>] See \{#keywords}
18
+ # @param separator [String] See \{List#separator}.
19
+ def initialize(value, keywords, separator)
20
+ super(value, separator)
21
+ @keywords = keywords
22
+ end
23
+
24
+ # The keyword arguments attached to this list.
25
+ #
26
+ # @return [Hash<String, Literal>]
27
+ def keywords
28
+ @keywords_accessed = true
29
+ @keywords
30
+ end
31
+
32
+ # @see Node#children
33
+ def children
34
+ super + @keywords.values
35
+ end
36
+
37
+ # @see Node#deep_copy
38
+ def deep_copy
39
+ node = super
40
+ node.instance_variable_set('@keywords',
41
+ Sass::Util.map_hash(@keywords) {|k, v| [k, v.deep_copy]})
42
+ node
43
+ end
44
+
45
+ protected
46
+
47
+ # @see Node#_perform
48
+ def _perform(environment)
49
+ self
50
+ end
51
+ end
52
+ end
@@ -23,13 +23,20 @@ module Sass
23
23
  # @return [{String => Script::Node}]
24
24
  attr_reader :keywords
25
25
 
26
+ # The splat argument for this function, if one exists.
27
+ #
28
+ # @return [Script::Node?]
29
+ attr_accessor :splat
30
+
26
31
  # @param name [String] See \{#name}
27
32
  # @param args [Array<Script::Node>] See \{#args}
33
+ # @param splat [Script::Node] See \{#splat}
28
34
  # @param keywords [{String => Script::Node}] See \{#keywords}
29
- def initialize(name, args, keywords)
35
+ def initialize(name, args, keywords, splat)
30
36
  @name = name
31
37
  @args = args
32
38
  @keywords = keywords
39
+ @splat = splat
33
40
  super()
34
41
  end
35
42
 
@@ -38,7 +45,11 @@ module Sass
38
45
  args = @args.map {|a| a.inspect}.join(', ')
39
46
  keywords = Sass::Util.hash_to_a(@keywords).
40
47
  map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
41
- "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
48
+ if self.splat
49
+ splat = (args.empty? && keywords.empty?) ? "" : ", "
50
+ splat = "#{splat}#{self.splat.inspect}..."
51
+ end
52
+ "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
42
53
  end
43
54
 
44
55
  # @see Node#to_sass
@@ -46,7 +57,11 @@ module Sass
46
57
  args = @args.map {|a| a.to_sass(opts)}.join(', ')
47
58
  keywords = Sass::Util.hash_to_a(@keywords).
48
59
  map {|k, v| "$#{dasherize(k, opts)}: #{v.to_sass(opts)}"}.join(', ')
49
- "#{dasherize(name, opts)}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
60
+ if self.splat
61
+ splat = (args.empty? && keywords.empty?) ? "" : ", "
62
+ splat = "#{splat}#{self.splat.inspect}..."
63
+ end
64
+ "#{dasherize(name, opts)}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
50
65
  end
51
66
 
52
67
  # Returns the arguments to the function.
@@ -54,7 +69,9 @@ module Sass
54
69
  # @return [Array<Node>]
55
70
  # @see Node#children
56
71
  def children
57
- @args + @keywords.values
72
+ res = @args + @keywords.values
73
+ res << @splat if @splat
74
+ res
58
75
  end
59
76
 
60
77
  # @see Node#deep_copy
@@ -74,13 +91,14 @@ module Sass
74
91
  # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
75
92
  def _perform(environment)
76
93
  args = @args.map {|a| a.perform(environment)}
94
+ splat = @splat.perform(environment) if @splat
77
95
  if fn = environment.function(@name)
78
96
  keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
79
- return perform_sass_fn(fn, args, keywords)
97
+ return perform_sass_fn(fn, args, keywords, splat)
80
98
  end
81
99
 
82
100
  ruby_name = @name.tr('-', '_')
83
- args = construct_ruby_args(ruby_name, args, environment)
101
+ args = construct_ruby_args(ruby_name, args, splat, environment)
84
102
 
85
103
  unless Functions.callable?(ruby_name)
86
104
  opts(to_literal(args))
@@ -106,12 +124,23 @@ module Sass
106
124
 
107
125
  private
108
126
 
109
- def construct_ruby_args(name, args, environment)
110
- unless signature = Functions.signature(name.to_sym, args.size, @keywords.size)
111
- return args if keywords.empty?
127
+ def construct_ruby_args(name, args, splat, environment)
128
+ args += splat.to_a if splat
129
+
130
+ # If variable arguments were passed, there won't be any explicit keywords.
131
+ if splat.is_a?(Sass::Script::ArgList)
132
+ kwargs_size = splat.keywords.size
133
+ splat.keywords_accessed = false
134
+ else
135
+ kwargs_size = @keywords.size
136
+ end
137
+
138
+ unless signature = Functions.signature(name.to_sym, args.size, kwargs_size)
139
+ return args if @keywords.empty?
112
140
  raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
113
141
  end
114
- keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
142
+ keywords = splat.is_a?(Sass::Script::ArgList) ? splat.keywords :
143
+ Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
115
144
 
116
145
  # If the user passes more non-keyword args than the function expects,
117
146
  # but it does expect keyword args, Ruby's arg handling won't raise an error.
@@ -148,36 +177,14 @@ module Sass
148
177
  args
149
178
  end
150
179
 
151
- def perform_sass_fn(function, args, keywords)
152
- # TODO: merge with mixin arg evaluation?
153
- if keywords.any?
154
- unknown_args = keywords.keys - function.args.map {|var| var.first.underscored_name }
155
- if unknown_args.any?
156
- raise Sass::SyntaxError.new("Function #{@name} doesn't have #{unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'} #{unknown_args.map{|name| "$#{name}"}.join ', '}")
180
+ def perform_sass_fn(function, args, keywords, splat)
181
+ Sass::Tree::Visitors::Perform.perform_arguments(function, args, keywords, splat) do |env|
182
+ val = catch :_sass_return do
183
+ function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, env)}
184
+ raise Sass::SyntaxError.new("Function #{@name} finished without @return")
157
185
  end
186
+ val
158
187
  end
159
-
160
- if args.size > function.args.size
161
- raise ArgumentError.new("Wrong number of arguments (#{args.size} for #{function.args.size})")
162
- end
163
-
164
- environment = function.args.zip(args).
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
-
170
- env.set_local_var(var.name,
171
- value || keywords[var.underscored_name] || (default && default.perform(env)))
172
- raise Sass::SyntaxError.new("Function #{@name} is missing argument #{var.inspect}") unless env.var(var.name)
173
- env
174
- end
175
-
176
- val = catch :_sass_return do
177
- function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, environment)}
178
- raise Sass::SyntaxError.new("Function #{@name} finished without @return")
179
- end
180
- val
181
188
  end
182
189
  end
183
190
  end
@@ -62,6 +62,7 @@ module Sass
62
62
  '}' => :end_interpolation,
63
63
  ';' => :semicolon,
64
64
  '{' => :lcurly,
65
+ '...' => :splat,
65
66
  }
66
67
 
67
68
  OPERATORS_REVERSE = Sass::Util.map_hash(OPERATORS) {|k, v| [v, k]}
@@ -34,7 +34,7 @@ module Sass::Script
34
34
  # @see Node#eq
35
35
  def eq(other)
36
36
  Sass::Script::Bool.new(
37
- self.class == other.class && self.value == other.value &&
37
+ other.is_a?(List) && self.value == other.value &&
38
38
  self.separator == other.separator)
39
39
  end
40
40
 
@@ -11,6 +11,7 @@ module Sass::Script
11
11
  require 'sass/script/bool'
12
12
  require 'sass/script/null'
13
13
  require 'sass/script/list'
14
+ require 'sass/script/arg_list'
14
15
 
15
16
  # Returns the Ruby value of the literal.
16
17
  # The type of this value varies based on the subclass.
@@ -422,12 +422,13 @@ module Sass::Script
422
422
  end
423
423
 
424
424
  # A hash of unit names to their index in the conversion table
425
- CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4}
426
- CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 ], # in
427
- [ nil, 1, 2.36220473, 10, 28.3464567], # cm
428
- [ nil, nil, 1, 4.23333333, 12 ], # pc
429
- [ nil, nil, nil, 1, 2.83464567], # mm
430
- [ nil, nil, nil, nil, 1 ]] # pt
425
+ CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4, "px" => 5 }
426
+ CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 , 96 ], # in
427
+ [ nil, 1, 2.36220473, 10, 28.3464567, 37.795275591], # cm
428
+ [ nil, nil, 1, 4.23333333, 12 , 16 ], # pc
429
+ [ nil, nil, nil, 1, 2.83464567, 3.7795275591], # mm
430
+ [ nil, nil, nil, nil, 1 , 1.3333333333], # pt
431
+ [ nil, nil, nil, nil, nil , 1 ]] # px
431
432
 
432
433
  def conversion_factor(from_unit, to_unit)
433
434
  res = CONVERSION_TABLE[CONVERTABLE_UNITS[from_unit]][CONVERTABLE_UNITS[to_unit]]
@@ -74,21 +74,22 @@ module Sass
74
74
 
75
75
  # Parses the argument list for a mixin include.
76
76
  #
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.
77
+ # @return [(Array<Script::Node>, {String => Script::Node}, Script::Node)]
78
+ # The root nodes of the positional arguments, keyword arguments, and
79
+ # splat argument. Keyword arguments are in a hash from names to values.
80
80
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
81
81
  def parse_mixin_include_arglist
82
82
  args, keywords = [], {}
83
83
  if try_tok(:lparen)
84
- args, keywords = mixin_arglist || [[], {}]
84
+ args, keywords, splat = mixin_arglist || [[], {}]
85
85
  assert_tok(:rparen)
86
86
  end
87
87
  assert_done
88
88
 
89
89
  args.each {|a| a.options = @options}
90
90
  keywords.each {|k, v| v.options = @options}
91
- return args, keywords
91
+ splat.options = @options if splat
92
+ return args, keywords, splat
92
93
  rescue Sass::SyntaxError => e
93
94
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
94
95
  raise e
@@ -96,17 +97,19 @@ module Sass
96
97
 
97
98
  # Parses the argument list for a mixin definition.
98
99
  #
99
- # @return [Array<Script::Node>] The root nodes of the arguments.
100
+ # @return [(Array<Script::Node>, Script::Node)]
101
+ # The root nodes of the arguments, and the splat argument.
100
102
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
101
103
  def parse_mixin_definition_arglist
102
- args = defn_arglist!(false)
104
+ args, splat = defn_arglist!(false)
103
105
  assert_done
104
106
 
105
107
  args.each do |k, v|
106
108
  k.options = @options
107
109
  v.options = @options if v
108
110
  end
109
- args
111
+ splat.options = @options if splat
112
+ return args, splat
110
113
  rescue Sass::SyntaxError => e
111
114
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
112
115
  raise e
@@ -114,17 +117,19 @@ module Sass
114
117
 
115
118
  # Parses the argument list for a function definition.
116
119
  #
117
- # @return [Array<Script::Node>] The root nodes of the arguments.
120
+ # @return [(Array<Script::Node>, Script::Node)]
121
+ # The root nodes of the arguments, and the splat argument.
118
122
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
119
123
  def parse_function_definition_arglist
120
- args = defn_arglist!(true)
124
+ args, splat = defn_arglist!(true)
121
125
  assert_done
122
126
 
123
127
  args.each do |k, v|
124
128
  k.options = @options
125
129
  v.options = @options if v
126
130
  end
127
- args
131
+ splat.options = @options if splat
132
+ return args, splat
128
133
  rescue Sass::SyntaxError => e
129
134
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
130
135
  raise e
@@ -324,37 +329,41 @@ RUBY
324
329
 
325
330
  def funcall
326
331
  return raw unless tok = try_tok(:funcall)
327
- args, keywords = fn_arglist || [[], {}]
332
+ args, keywords, splat = fn_arglist || [[], {}]
328
333
  assert_tok(:rparen)
329
- node(Script::Funcall.new(tok.value, args, keywords))
334
+ node(Script::Funcall.new(tok.value, args, keywords, splat))
330
335
  end
331
336
 
332
337
  def defn_arglist!(must_have_parens)
333
338
  if must_have_parens
334
339
  assert_tok(:lparen)
335
340
  else
336
- return [] unless try_tok(:lparen)
341
+ return [], nil unless try_tok(:lparen)
337
342
  end
338
- return [] if try_tok(:rparen)
343
+ return [], nil if try_tok(:rparen)
339
344
 
340
345
  res = []
346
+ splat = nil
341
347
  must_have_default = false
342
348
  loop do
343
349
  line = @lexer.line
344
350
  offset = @lexer.offset + 1
345
351
  c = assert_tok(:const)
346
352
  var = Script::Variable.new(c.value)
347
- if tok = try_tok(:colon)
353
+ if try_tok(:colon)
348
354
  val = assert_expr(:space)
349
355
  must_have_default = true
350
356
  elsif must_have_default
351
357
  raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
358
+ elsif try_tok(:splat)
359
+ splat = var
360
+ break
352
361
  end
353
362
  res << [var, val]
354
363
  break unless try_tok(:comma)
355
364
  end
356
365
  assert_tok(:rparen)
357
- res
366
+ return res, splat
358
367
  end
359
368
 
360
369
  def fn_arglist
@@ -376,34 +385,23 @@ RUBY
376
385
 
377
386
  unless try_tok(:comma)
378
387
  return [], keywords if keywords
388
+ return [], {}, e if try_tok(:splat)
379
389
  return [e], {}
380
390
  end
381
391
 
382
- other_args, other_keywords = assert_expr(type)
392
+ other_args, other_keywords, splat = assert_expr(type)
383
393
  if keywords
384
- if !other_args.empty?
385
- raise SyntaxError.new("Positional arguments must come before keyword arguments")
394
+ if !other_args.empty? || splat
395
+ raise SyntaxError.new("Positional arguments must come before keyword arguments.")
386
396
  elsif other_keywords[name.underscored_name]
387
397
  raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
388
398
  end
389
- return other_args, keywords.merge(other_keywords)
399
+ return other_args, keywords.merge(other_keywords), splat
390
400
  else
391
- return [e, *other_args], other_keywords
401
+ return [e, *other_args], other_keywords, splat
392
402
  end
393
403
  end
394
404
 
395
- def keyword_arglist
396
- return unless var = try_tok(:const)
397
- unless try_tok(:colon)
398
- return_tok!
399
- return
400
- end
401
- name = var[1]
402
- value = interpolation
403
- return {name => value} unless try_tok(:comma)
404
- {name => value}.merge(assert_expr(:keyword_arglist))
405
- end
406
-
407
405
  def raw
408
406
  return special_fun unless tok = try_tok(:raw)
409
407
  node(Script::String.new(tok.value))