sass 3.2.0.alpha.60 → 3.2.0.alpha.61

Sign up to get free protection for your applications and to get access to all the features.
data/REVISION CHANGED
@@ -1 +1 @@
1
- 3b7b3977a280d7117d185295abde521a6860f893
1
+ f03f995506891014aea1df1a16672298469b3966
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.0.alpha.60
1
+ 3.2.0.alpha.61
@@ -53,6 +53,7 @@ module Sass
53
53
  # @param key [String]
54
54
  # @return [String] The path to the cache file.
55
55
  def path_to(key)
56
+ key = key.gsub(/[<>:\\|?*%]/) {|c| "%%%03d" % Sass::Util.ord(c)}
56
57
  File.join(cache_location, key)
57
58
  end
58
59
  end
data/lib/sass/engine.rb CHANGED
@@ -224,7 +224,7 @@ module Sass
224
224
  # If you're compiling a single Sass file from the filesystem,
225
225
  # use \{Sass::Engine.for\_file}.
226
226
  # If you're compiling multiple files from the filesystem,
227
- # use {Sass::Plugin.
227
+ # use {Sass::Plugin}.
228
228
  #
229
229
  # @param template [String] The Sass template.
230
230
  # This template can be encoded using any encoding
data/lib/sass/media.rb CHANGED
@@ -51,6 +51,20 @@ module Sass::Media
51
51
  def to_src(options)
52
52
  queries.map {|q| q.to_src(options)}.join(', ')
53
53
  end
54
+
55
+ # Returns a deep copy of this query list and all its children.
56
+ #
57
+ # @return [QueryList]
58
+ def deep_copy
59
+ QueryList.new(queries.map {|q| q.deep_copy})
60
+ end
61
+
62
+ # Sets the options hash for the script nodes in the media query.
63
+ #
64
+ # @param options [{Symbol => Object}] The options has to set.
65
+ def options=(options)
66
+ queries.each {|q| q.options = options}
67
+ end
54
68
  end
55
69
 
56
70
  # A single media query.
@@ -164,6 +178,25 @@ module Sass::Media
164
178
  src << expressions.map {|e| e.to_src(options)}.join(' and ')
165
179
  src
166
180
  end
181
+
182
+ # Returns a deep copy of this query and all its children.
183
+ #
184
+ # @return [Query]
185
+ def deep_copy
186
+ Query.new(
187
+ modifier.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c},
188
+ type.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c},
189
+ expressions.map {|q| q.deep_copy})
190
+ end
191
+
192
+ # Sets the options hash for the script nodes in the media query.
193
+ #
194
+ # @param options [{Symbol => Object}] The options has to set.
195
+ def options=(options)
196
+ modifier.each {|m| m.options = options if m.is_a?(Sass::Script::Node)}
197
+ type.each {|t| t.options = options if t.is_a?(Sass::Script::Node)}
198
+ expressions.each {|e| e.options = options}
199
+ end
167
200
  end
168
201
 
169
202
  # A media query expression.
@@ -232,6 +265,23 @@ module Sass::Media
232
265
  src << ')'
233
266
  src
234
267
  end
268
+
269
+ # Returns a deep copy of this expression.
270
+ #
271
+ # @return [Expression]
272
+ def deep_copy
273
+ Expression.new(
274
+ name.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c},
275
+ value.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c})
276
+ end
277
+
278
+ # Sets the options hash for the script nodes in the expression.
279
+ #
280
+ # @param options [{Symbol => Object}] The options has to set.
281
+ def options=(options)
282
+ name.each {|n| n.options = options if n.is_a?(Sass::Script::Node)}
283
+ value.each {|v| v.options = options if v.is_a?(Sass::Script::Node)}
284
+ end
235
285
  end
236
286
 
237
287
  # Converts an interpolation array that may represent a single variable to source.
@@ -36,7 +36,7 @@ module Sass
36
36
  # @return [String] A string representation of the function call
37
37
  def inspect
38
38
  args = @args.map {|a| a.inspect}.join(', ')
39
- keywords = @keywords.sort_by {|k, v| k}.
39
+ keywords = Sass::Util.hash_to_a(@keywords).
40
40
  map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
41
41
  "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
42
42
  end
@@ -44,7 +44,7 @@ module Sass
44
44
  # @see Node#to_sass
45
45
  def to_sass(opts = {})
46
46
  args = @args.map {|a| a.to_sass(opts)}.join(', ')
47
- keywords = @keywords.sort_by {|k, v| k}.
47
+ keywords = Sass::Util.hash_to_a(@keywords).
48
48
  map {|k, v| "$#{dasherize(k, opts)}: #{v.to_sass(opts)}"}.join(', ')
49
49
  "#{dasherize(name, opts)}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
50
50
  end
@@ -55,26 +55,6 @@ The #options attribute is not set on this #{self.class}.
55
55
  MSG
56
56
  end
57
57
 
58
- # The SassScript `and` operation.
59
- #
60
- # @param other [Literal] The right-hand side of the operator
61
- # @return [Literal] The result of a logical and:
62
- # `other` if this literal isn't a false {Bool},
63
- # and this literal otherwise
64
- def and(other)
65
- to_bool ? other : self
66
- end
67
-
68
- # The SassScript `or` operation.
69
- #
70
- # @param other [Literal] The right-hand side of the operator
71
- # @return [Literal] The result of the logical or:
72
- # this literal if it isn't a false {Bool},
73
- # and `other` otherwise
74
- def or(other)
75
- to_bool ? self : other
76
- end
77
-
78
58
  # The SassScript `==` operation.
79
59
  # **Note that this returns a {Sass::Script::Bool} object,
80
60
  # not a Ruby boolean**.
@@ -72,6 +72,14 @@ module Sass::Script
72
72
  # @raise [Sass::SyntaxError] if the operation is undefined for the operands
73
73
  def _perform(environment)
74
74
  literal1 = @operand1.perform(environment)
75
+
76
+ # Special-case :and and :or to support short-circuiting.
77
+ if @operator == :and
78
+ return literal1.to_bool ? @operand2.perform(environment) : literal1
79
+ elsif @operator == :or
80
+ return literal1.to_bool ? literal1 : @operand2.perform(environment)
81
+ end
82
+
75
83
  literal2 = @operand2.perform(environment)
76
84
 
77
85
  begin
data/lib/sass/selector.rb CHANGED
@@ -379,7 +379,7 @@ module Sass
379
379
  attr_reader :selector
380
380
 
381
381
  # @param [String] The name of the pseudoclass
382
- # @param [Selector::Sequence] The selector argument
382
+ # @param [Selector::CommaSequence] The selector argument
383
383
  def initialize(name, selector)
384
384
  @name = name
385
385
  @selector = selector
@@ -124,15 +124,15 @@ module Sass
124
124
  # @param path [Array<Array<SimpleSequence or String>>] A list of parenthesized selector groups.
125
125
  # @return [Array<Array<SimpleSequence or String>>] A list of fully-expanded selectors.
126
126
  def weave(path)
127
+ # This function works by moving through the selector path left-to-right,
128
+ # building all possible prefixes simultaneously. These prefixes are
129
+ # `befores`, while the remaining parenthesized suffixes is `afters`.
127
130
  befores = [[]]
128
131
  afters = path.dup
129
132
 
130
133
  until afters.empty?
131
134
  current = afters.shift.dup
132
135
  last_current = [current.pop]
133
- while !current.empty? && last_current.first.is_a?(String) || current.last.is_a?(String)
134
- last_current.unshift(current.pop)
135
- end
136
136
  befores = Sass::Util.flatten(befores.map do |before|
137
137
  next [] unless sub = subweave(before, current)
138
138
  sub.map {|seqs| seqs + last_current}
@@ -150,6 +150,12 @@ module Sass
150
150
  # `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`,
151
151
  # and so on until `.baz .bang .foo .bar`.
152
152
  #
153
+ # Semantically, for selectors A and B, this returns all selectors `AB_i`
154
+ # such that the union over all i of elements matched by `AB_i X` is
155
+ # identical to the intersection of all elements matched by `A X` and all
156
+ # elements matched by `B X`. Some `AB_i` are elided to reduce the size of
157
+ # the output.
158
+ #
153
159
  # @param seq1 [Array<SimpleSequence or String>]
154
160
  # @param seq2 [Array<SimpleSequence or String>]
155
161
  # @return [Array<Array<SimpleSequence or String>>]
@@ -157,31 +163,34 @@ module Sass
157
163
  return [seq2] if seq1.empty?
158
164
  return [seq1] if seq2.empty?
159
165
 
166
+ seq1, seq2 = seq1.dup, seq2.dup
160
167
  return unless init = merge_initial_ops(seq1, seq2)
168
+ return unless fin = merge_final_ops(seq1, seq2)
161
169
  seq1 = group_selectors(seq1)
162
170
  seq2 = group_selectors(seq2)
163
171
  lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2|
164
172
  next s1 if s1 == s2
165
173
  next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
166
- next s2 if subweave_superselector?(s1, s2)
167
- next s1 if subweave_superselector?(s2, s1)
174
+ next s2 if superselector?(s1, s2)
175
+ next s1 if superselector?(s2, s1)
168
176
  end
169
177
 
170
178
  diff = [[init]]
171
179
  until lcs.empty?
172
- diff << chunks(seq1, seq2) {|s| subweave_superselector?(s.first, lcs.first)} << [lcs.shift]
180
+ diff << chunks(seq1, seq2) {|s| superselector?(s.first, lcs.first)} << [lcs.shift]
173
181
  seq1.shift
174
182
  seq2.shift
175
183
  end
176
184
  diff << chunks(seq1, seq2) {|s| s.empty?}
185
+ diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]}
177
186
  diff.reject! {|c| c.empty?}
178
187
 
179
188
  Sass::Util.paths(diff).map {|p| p.flatten}
180
189
  end
181
190
 
182
- # Extracts initial selector operators (`"+"`, `">"`, `"~"`, and `"\n"`)
191
+ # Extracts initial selector combinators (`"+"`, `">"`, `"~"`, and `"\n"`)
183
192
  # from two sequences and merges them together into a single array of
184
- # selector operators.
193
+ # selector combinators.
185
194
  #
186
195
  # @param seq1 [Array<SimpleSequence or String>]
187
196
  # @param seq2 [Array<SimpleSequence or String>]
@@ -204,6 +213,100 @@ module Sass
204
213
  return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
205
214
  end
206
215
 
216
+ # Extracts final selector combinators (`"+"`, `">"`, `"~"`) and the
217
+ # selectors to which they apply from two sequences and merges them
218
+ # together into a single array.
219
+ #
220
+ # @param seq1 [Array<SimpleSequence or String>]
221
+ # @param seq2 [Array<SimpleSequence or String>]
222
+ # @return [Array<SimpleSequence or String or
223
+ # Array<Array<SimpleSequence or String>>]
224
+ # If there are no trailing combinators to be merged, this will be the
225
+ # empty array. If the trailing combinators cannot be merged, this will
226
+ # be nil. Otherwise, this will contained the merged selector. Array
227
+ # elements are [Sass::Util#paths]-style options; conceptually, an "or"
228
+ # of multiple selectors.
229
+ def merge_final_ops(seq1, seq2, res = [])
230
+ ops1, ops2 = [], []
231
+ ops1 << seq1.pop while seq1.last.is_a?(String)
232
+ ops2 << seq2.pop while seq2.last.is_a?(String)
233
+
234
+ # Not worth the headache of trying to preserve newlines here. The most
235
+ # important use of newlines is at the beginning of the selector to wrap
236
+ # across lines anyway.
237
+ ops1.reject! {|o| o == "\n"}
238
+ ops2.reject! {|o| o == "\n"}
239
+
240
+ return res if ops1.empty? && ops2.empty?
241
+ if ops1.size > 1 || ops2.size > 1
242
+ # If there are multiple operators, something hacky's going on. If one
243
+ # is a supersequence of the other, use that, otherwise give up.
244
+ lcs = Sass::Util.lcs(ops1, ops2)
245
+ return unless lcs == ops1 || lcs == ops2
246
+ res.unshift *(ops1.size > ops2.size ? ops1 : ops2).reverse
247
+ return res
248
+ end
249
+
250
+ # This code looks complicated, but it's actually just a bunch of special
251
+ # cases for interactions between different combinators.
252
+ op1, op2 = ops1.first, ops2.first
253
+ if op1 && op2
254
+ sel1 = seq1.pop
255
+ sel2 = seq2.pop
256
+ if op1 == '~' && op2 == '~'
257
+ if superselector?([sel1], [sel2])
258
+ res.unshift sel2, '~'
259
+ elsif superselector?([sel2], [sel1])
260
+ res.unshift sel1, '~'
261
+ else
262
+ merged = sel1.unify(sel2.members)
263
+ res.unshift [
264
+ [sel1, '~', sel2, '~'],
265
+ [sel2, '~', sel1, '~'],
266
+ ([merged, '~'] if merged)
267
+ ].compact
268
+ end
269
+ elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~')
270
+ if op1 == '~'
271
+ tilde_sel, plus_sel = sel1, sel2
272
+ else
273
+ tilde_sel, plus_sel = sel2, sel1
274
+ end
275
+
276
+ if superselector?([tilde_sel], [plus_sel])
277
+ res.unshift plus_sel, '+'
278
+ else
279
+ merged = plus_sel.unify(tilde_sel.members)
280
+ res.unshift [
281
+ [tilde_sel, '~', plus_sel, '+'],
282
+ ([merged, '+'] if merged)
283
+ ].compact
284
+ end
285
+ elsif op1 == '>' && %w[~ +].include?(op2)
286
+ res.unshift sel2, op2
287
+ seq1.push sel1, op1
288
+ elsif op2 == '>' && %w[~ +].include?(op1)
289
+ res.unshift sel1, op1
290
+ seq2.push sel2, op2
291
+ elsif op1 == op2
292
+ return unless merged = sel1.unify(sel2.members)
293
+ res.unshift merged, op1
294
+ else
295
+ # Unknown selector combinators can't be unified
296
+ return
297
+ end
298
+ return merge_final_ops(seq1, seq2, res)
299
+ elsif op1
300
+ seq2.pop if op1 == '>' && seq2.last && superselector?([seq2.last], [seq1.last])
301
+ res.unshift seq1.pop, op1
302
+ return merge_final_ops(seq1, seq2, res)
303
+ else # op2
304
+ seq1.pop if op2 == '>' && seq1.last && superselector?([seq1.last], [seq2.last])
305
+ res.unshift seq2.pop, op2
306
+ return merge_final_ops(seq1, seq2, res)
307
+ end
308
+ end
309
+
207
310
  # Takes initial subsequences of `seq1` and `seq2` and returns all
208
311
  # orderings of those subsequences. The initial subsequences are determined
209
312
  # by a block.
@@ -223,6 +326,7 @@ module Sass
223
326
  # cutting off some initial subsequence.
224
327
  # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence
225
328
  # here.
329
+ # @return [Array<Array>] All possible orderings of the initial subsequences.
226
330
  def chunks(seq1, seq2)
227
331
  chunk1 = []
228
332
  chunk1 << seq1.shift until yield seq1
@@ -257,25 +361,33 @@ module Sass
257
361
  end
258
362
 
259
363
  # Given two sequences of simple selectors, returns whether `sseq1` is a
260
- # superselector of `sseq2`.
364
+ # superselector of `sseq2`; that is, whether `sseq1` matches every element
365
+ # `sseq2` matches.
366
+ #
367
+ # Both `sseq1` and `sseq2` are of the form
368
+ # `SimpleSelector (String SimpleSelector)* String*`, although selectors
369
+ # with a trailing operator are considered to be neither superselectors nor
370
+ # subselectors.
261
371
  #
262
- # @param sseq1 [Array<SimpleSelector or String>]
263
- # @param sseq2 [Array<SimpleSelector or String>]
372
+ # @param sseq1 [Array<SimpleSequence or String>]
373
+ # @param sseq2 [Array<SimpleSequence or String>]
264
374
  # @return [Boolean]
265
- def subweave_superselector?(sseq1, sseq2)
375
+ def superselector?(sseq1, sseq2)
376
+ sseq1 = sseq1.reject {|e| e == "\n"}
377
+ sseq2 = sseq2.reject {|e| e == "\n"}
378
+ # Selectors with trailing operators are neither superselectors nor
379
+ # subselectors.
380
+ return if sseq1.last.is_a?(String) || sseq2.last.is_a?(String)
266
381
  if sseq1.size > 1
267
382
  # More complex selectors are never superselectors of less complex ones
268
383
  return unless sseq2.size > 1
269
384
  # .foo ~ .bar is a superselector of .foo + .bar
270
385
  return unless sseq1[1] == "~" ? sseq2[1] != ">" : sseq2[1] == sseq1[1]
271
386
  return unless sseq1.first.superselector?(sseq2.first)
272
- return true if sseq1.size == 2
273
- return false if sseq2.size == 2
274
- return subweave_superselector?(sseq1[2..-1], sseq2[2..-1])
387
+ return superselector?(sseq1[2..-1], sseq2[2..-1])
275
388
  elsif sseq2.size > 1
276
389
  return true if sseq2[1] == ">" && sseq1.first.superselector?(sseq2.first)
277
- return false if sseq2.size == 2
278
- return subweave_superselector?(sseq1, sseq2[2..-1])
390
+ return superselector?(sseq1, sseq2[2..-1])
279
391
  else
280
392
  sseq1.first.superselector?(sseq2.first)
281
393
  end
@@ -168,7 +168,8 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
168
168
  def visit_mixin(node)
169
169
  unless node.args.empty? && node.keywords.empty?
170
170
  args = node.args.map {|a| a.to_sass(@options)}.join(", ")
171
- keywords = node.keywords.map {|k, v| "$#{dasherize(k)}: #{v.to_sass(@options)}"}.join(', ')
171
+ keywords = Sass::Util.hash_to_a(node.keywords).
172
+ map {|k, v| "$#{dasherize(k)}: #{v.to_sass(@options)}"}.join(', ')
172
173
  arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
173
174
  end
174
175
  "#{tab_str}#{@format == :sass ? '+' : '@include '}#{dasherize(node.name)}#{arglist}#{node.has_children ? yield : semi}\n"
@@ -52,12 +52,27 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
52
52
  def visit_root(node)
53
53
  yield
54
54
 
55
- # In Ruby 1.9 we can make all @charset nodes invisible
56
- # and infer the final @charset from the encoding of the final string.
57
- if Sass::Util.ruby1_8? && parent.nil?
58
- charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
59
- node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
60
- node.children.unshift charset if charset
55
+ if parent.nil?
56
+ # In Ruby 1.9 we can make all @charset nodes invisible
57
+ # and infer the final @charset from the encoding of the final string.
58
+ if Sass::Util.ruby1_8?
59
+ charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
60
+ node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
61
+ node.children.unshift charset if charset
62
+ end
63
+
64
+ imports = Sass::Util.extract!(node.children) do |c|
65
+ c.is_a?(Sass::Tree::DirectiveNode) && !c.is_a?(Sass::Tree::MediaNode) &&
66
+ c.resolved_value =~ /^@import /i
67
+ end
68
+ charset_and_index = Sass::Util.ruby1_8? &&
69
+ node.children.each_with_index.find {|c, _| c.is_a?(Sass::Tree::CharsetNode)}
70
+ if charset_and_index
71
+ index = charset_and_index.last
72
+ node.children = node.children[0..index] + imports + node.children[index+1..-1]
73
+ else
74
+ node.children = imports + node.children
75
+ end
61
76
  end
62
77
 
63
78
  return node, @extends
@@ -84,4 +84,14 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
84
84
  node.expr = node.expr.deep_copy
85
85
  yield
86
86
  end
87
+
88
+ def visit_directive(node)
89
+ node.value = node.value.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
90
+ yield
91
+ end
92
+
93
+ def visit_media(node)
94
+ node.query = query.deep_copy
95
+ yield
96
+ end
87
97
  end