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

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/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))