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

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