sass 3.1.12 → 3.1.13
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/VERSION +1 -1
- data/lib/sass/cache_stores/filesystem.rb +1 -0
- data/lib/sass/css.rb +0 -1
- data/lib/sass/engine.rb +4 -5
- data/lib/sass/script.rb +0 -1
- data/lib/sass/script/funcall.rb +2 -2
- data/lib/sass/script/lexer.rb +1 -3
- data/lib/sass/script/literal.rb +0 -20
- data/lib/sass/script/operation.rb +8 -0
- data/lib/sass/scss/parser.rb +2 -3
- data/lib/sass/selector.rb +15 -3
- data/lib/sass/selector/sequence.rb +65 -5
- data/lib/sass/selector/simple.rb +1 -1
- data/lib/sass/shared.rb +2 -4
- data/lib/sass/tree/visitors/convert.rb +2 -1
- data/lib/sass/tree/visitors/cssize.rb +20 -6
- data/lib/sass/tree/visitors/perform.rb +11 -0
- data/lib/sass/tree/visitors/to_css.rb +11 -3
- data/lib/sass/util.rb +34 -1
- data/lib/sass/util/multibyte_string_scanner.rb +134 -0
- data/test/sass/engine_test.rb +65 -6
- data/test/sass/extend_test.rb +88 -0
- data/test/sass/more_results/more_import.css +2 -2
- data/test/sass/results/import.css +2 -2
- data/test/sass/results/import_charset.css +1 -0
- data/test/sass/results/import_charset_1_8.css +1 -0
- data/test/sass/results/import_charset_ibm866.css +1 -0
- data/test/sass/results/scss_import.css +2 -2
- data/test/sass/script_test.rb +5 -0
- data/test/sass/scss/scss_test.rb +3 -3
- data/test/sass/templates/import_charset.sass +2 -0
- data/test/sass/templates/import_charset_1_8.sass +2 -0
- data/test/sass/templates/import_charset_ibm866.sass +2 -0
- data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
- data/test/sass/util_test.rb +6 -0
- metadata +7 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.1.
|
1
|
+
3.1.13
|
data/lib/sass/css.rb
CHANGED
data/lib/sass/engine.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'strscan'
|
2
1
|
require 'set'
|
3
2
|
require 'digest/sha1'
|
4
3
|
require 'sass/cache_stores'
|
@@ -219,7 +218,7 @@ module Sass
|
|
219
218
|
# If you're compiling a single Sass file from the filesystem,
|
220
219
|
# use \{Sass::Engine.for\_file}.
|
221
220
|
# If you're compiling multiple files from the filesystem,
|
222
|
-
# use {Sass::Plugin.
|
221
|
+
# use {Sass::Plugin}.
|
223
222
|
#
|
224
223
|
# @param template [String] The Sass template.
|
225
224
|
# This template can be encoded using any encoding
|
@@ -565,7 +564,7 @@ WARNING
|
|
565
564
|
end
|
566
565
|
|
567
566
|
def parse_property_or_rule(line)
|
568
|
-
scanner =
|
567
|
+
scanner = Sass::Util::MultibyteStringScanner.new(line.text)
|
569
568
|
hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
|
570
569
|
parser = Sass::SCSS::SassParser.new(scanner, @options[:filename], @line)
|
571
570
|
|
@@ -749,7 +748,7 @@ WARNING
|
|
749
748
|
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
|
750
749
|
:line => @line + 1) unless line.children.empty?
|
751
750
|
|
752
|
-
scanner =
|
751
|
+
scanner = Sass::Util::MultibyteStringScanner.new(value)
|
753
752
|
values = []
|
754
753
|
|
755
754
|
loop do
|
@@ -783,7 +782,7 @@ WARNING
|
|
783
782
|
elsif uri
|
784
783
|
Tree::DirectiveNode.new("@import #{uri}")
|
785
784
|
elsif val =~ /^http:\/\//
|
786
|
-
Tree::DirectiveNode.new("@import
|
785
|
+
Tree::DirectiveNode.new("@import #{str}")
|
787
786
|
else
|
788
787
|
Tree::ImportNode.new(val)
|
789
788
|
end
|
data/lib/sass/script.rb
CHANGED
data/lib/sass/script/funcall.rb
CHANGED
@@ -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.
|
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.
|
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
|
data/lib/sass/script/lexer.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'sass/scss/rx'
|
2
2
|
|
3
|
-
require 'strscan'
|
4
|
-
|
5
3
|
module Sass
|
6
4
|
module Script
|
7
5
|
# The lexical analyzer for SassScript.
|
@@ -126,7 +124,7 @@ module Sass
|
|
126
124
|
# @param options [{Symbol => Object}] An options hash;
|
127
125
|
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
|
128
126
|
def initialize(str, line, offset, options)
|
129
|
-
@scanner = str.is_a?(StringScanner) ? str :
|
127
|
+
@scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str)
|
130
128
|
@line = line
|
131
129
|
@offset = offset
|
132
130
|
@options = options
|
data/lib/sass/script/literal.rb
CHANGED
@@ -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/scss/parser.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'strscan'
|
2
1
|
require 'set'
|
3
2
|
|
4
3
|
module Sass
|
@@ -50,7 +49,7 @@ module Sass
|
|
50
49
|
if @template.is_a?(StringScanner)
|
51
50
|
@template
|
52
51
|
else
|
53
|
-
|
52
|
+
Sass::Util::MultibyteStringScanner.new(@template.gsub("\r", ""))
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
@@ -906,7 +905,7 @@ MESSAGE
|
|
906
905
|
if @throw_err
|
907
906
|
throw :_sass_parser_error, err
|
908
907
|
else
|
909
|
-
@scanner =
|
908
|
+
@scanner = Sass::Util::MultibyteStringScanner.new(@scanner.string)
|
910
909
|
@scanner.pos = err[:pos]
|
911
910
|
@line = err[:line]
|
912
911
|
@expected = err[:expected]
|
data/lib/sass/selector.rb
CHANGED
@@ -287,6 +287,13 @@ module Sass
|
|
287
287
|
# @return [Symbol]
|
288
288
|
attr_reader :type
|
289
289
|
|
290
|
+
# Some psuedo-class-syntax selectors (`:after` and `:before)
|
291
|
+
# are actually considered pseudo-elements
|
292
|
+
# and must be at the end of the selector to function properly.
|
293
|
+
#
|
294
|
+
# @return [Array<String>]
|
295
|
+
FINAL_SELECTORS = %w[after before]
|
296
|
+
|
290
297
|
# The name of the selector.
|
291
298
|
#
|
292
299
|
# @return [Array<String, Sass::Script::Node>]
|
@@ -312,6 +319,10 @@ module Sass
|
|
312
319
|
@arg = arg
|
313
320
|
end
|
314
321
|
|
322
|
+
def final?
|
323
|
+
type == :class && FINAL_SELECTORS.include?(name.first)
|
324
|
+
end
|
325
|
+
|
315
326
|
# @see Selector#to_a
|
316
327
|
def to_a
|
317
328
|
res = [@type == :class ? ":" : "::"] + @name
|
@@ -319,8 +330,8 @@ module Sass
|
|
319
330
|
res
|
320
331
|
end
|
321
332
|
|
322
|
-
# Returns `nil` if this is a
|
323
|
-
# and `sels` contains a
|
333
|
+
# Returns `nil` if this is a pseudoelement selector
|
334
|
+
# and `sels` contains a pseudoelement selector different than this one.
|
324
335
|
#
|
325
336
|
# @see Selector#unify
|
326
337
|
def unify(sels)
|
@@ -328,6 +339,7 @@ module Sass
|
|
328
339
|
sel.is_a?(Pseudo) && sel.type == :element &&
|
329
340
|
(sel.name != self.name || sel.arg != self.arg)
|
330
341
|
end
|
342
|
+
return sels + [self] if final?
|
331
343
|
super
|
332
344
|
end
|
333
345
|
end
|
@@ -346,7 +358,7 @@ module Sass
|
|
346
358
|
attr_reader :selector
|
347
359
|
|
348
360
|
# @param [String] The name of the pseudoclass
|
349
|
-
# @param [Selector::
|
361
|
+
# @param [Selector::CommaSequence] The selector argument
|
350
362
|
def initialize(name, selector)
|
351
363
|
@name = name
|
352
364
|
@selector = selector
|
@@ -134,10 +134,11 @@ module Sass
|
|
134
134
|
last_current.unshift(current.pop)
|
135
135
|
end
|
136
136
|
befores = Sass::Util.flatten(befores.map do |before|
|
137
|
-
subweave(before, current)
|
137
|
+
next [] unless sub = subweave(before, current)
|
138
|
+
sub.map {|seqs| seqs + last_current}
|
138
139
|
end, 1)
|
139
|
-
return befores if afters.empty?
|
140
140
|
end
|
141
|
+
return befores
|
141
142
|
end
|
142
143
|
|
143
144
|
# This interweaves two lists of selectors,
|
@@ -149,14 +150,14 @@ module Sass
|
|
149
150
|
# `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`,
|
150
151
|
# and so on until `.baz .bang .foo .bar`.
|
151
152
|
#
|
152
|
-
# @overload def subweave(seq1, seq2)
|
153
153
|
# @param seq1 [Array<SimpleSequence or String>]
|
154
154
|
# @param seq2 [Array<SimpleSequence or String>]
|
155
155
|
# @return [Array<Array<SimpleSequence or String>>]
|
156
|
-
def subweave(seq1, seq2
|
156
|
+
def subweave(seq1, seq2)
|
157
157
|
return [seq2] if seq1.empty?
|
158
158
|
return [seq1] if seq2.empty?
|
159
159
|
|
160
|
+
return unless init = merge_initial_ops(seq1, seq2)
|
160
161
|
seq1 = group_selectors(seq1)
|
161
162
|
seq2 = group_selectors(seq2)
|
162
163
|
lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2|
|
@@ -166,7 +167,7 @@ module Sass
|
|
166
167
|
next s1 if subweave_superselector?(s2, s1)
|
167
168
|
end
|
168
169
|
|
169
|
-
diff = []
|
170
|
+
diff = [[init]]
|
170
171
|
until lcs.empty?
|
171
172
|
diff << chunks(seq1, seq2) {|s| subweave_superselector?(s.first, lcs.first)} << [lcs.shift]
|
172
173
|
seq1.shift
|
@@ -178,6 +179,50 @@ module Sass
|
|
178
179
|
Sass::Util.paths(diff).map {|p| p.flatten}
|
179
180
|
end
|
180
181
|
|
182
|
+
# Extracts initial selector operators (`"+"`, `">"`, `"~"`, and `"\n"`)
|
183
|
+
# from two sequences and merges them together into a single array of
|
184
|
+
# selector operators.
|
185
|
+
#
|
186
|
+
# @param seq1 [Array<SimpleSequence or String>]
|
187
|
+
# @param seq2 [Array<SimpleSequence or String>]
|
188
|
+
# @return [Array<String>, nil] If there are no operators in the merged
|
189
|
+
# sequence, this will be the empty array. If the operators cannot be
|
190
|
+
# merged, this will be nil.
|
191
|
+
def merge_initial_ops(seq1, seq2)
|
192
|
+
ops1, ops2 = [], []
|
193
|
+
ops1 << seq1.shift while seq1.first.is_a?(String)
|
194
|
+
ops2 << seq2.shift while seq2.first.is_a?(String)
|
195
|
+
|
196
|
+
newline = false
|
197
|
+
newline ||= !!ops1.shift if ops1.first == "\n"
|
198
|
+
newline ||= !!ops2.shift if ops2.first == "\n"
|
199
|
+
|
200
|
+
# If neither sequence is a subsequence of the other, they cannot be
|
201
|
+
# merged successfully
|
202
|
+
lcs = Sass::Util.lcs(ops1, ops2)
|
203
|
+
return unless lcs == ops1 || lcs == ops2
|
204
|
+
return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Takes initial subsequences of `seq1` and `seq2` and returns all
|
208
|
+
# orderings of those subsequences. The initial subsequences are determined
|
209
|
+
# by a block.
|
210
|
+
#
|
211
|
+
# Destructively removes the initial subsequences of `seq1` and `seq2`.
|
212
|
+
#
|
213
|
+
# For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|`
|
214
|
+
# denoting the boundary of the initial subsequence), this would return
|
215
|
+
# `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and
|
216
|
+
# `(3 4 5)`.
|
217
|
+
#
|
218
|
+
# @param seq1 [Array]
|
219
|
+
# @param seq2 [Array]
|
220
|
+
# @yield [a] Used to determine when to cut off the initial subsequences.
|
221
|
+
# Called repeatedly for each sequence until it returns true.
|
222
|
+
# @yieldparam a [Array] A final subsequence of one input sequence after
|
223
|
+
# cutting off some initial subsequence.
|
224
|
+
# @yieldreturn [Boolean] Whether or not to cut off the initial subsequence
|
225
|
+
# here.
|
181
226
|
def chunks(seq1, seq2)
|
182
227
|
chunk1 = []
|
183
228
|
chunk1 << seq1.shift until yield seq1
|
@@ -189,6 +234,15 @@ module Sass
|
|
189
234
|
[chunk1 + chunk2, chunk2 + chunk1]
|
190
235
|
end
|
191
236
|
|
237
|
+
# Groups a sequence into subsequences. The subsequences are determined by
|
238
|
+
# strings; adjacent non-string elements will be put into separate groups,
|
239
|
+
# but any element adjacent to a string will be grouped with that string.
|
240
|
+
#
|
241
|
+
# For example, `(A B "C" D E "F" G "H" "I" J)` will become `[(A) (B "C" D)
|
242
|
+
# (E "F" G "H" "I" J)]`.
|
243
|
+
#
|
244
|
+
# @param seq [Array]
|
245
|
+
# @return [Array<Array>]
|
192
246
|
def group_selectors(seq)
|
193
247
|
newseq = []
|
194
248
|
tail = seq.dup
|
@@ -202,6 +256,12 @@ module Sass
|
|
202
256
|
return newseq
|
203
257
|
end
|
204
258
|
|
259
|
+
# Given two sequences of simple selectors, returns whether `sseq1` is a
|
260
|
+
# superselector of `sseq2`.
|
261
|
+
#
|
262
|
+
# @param sseq1 [Array<SimpleSelector or String>]
|
263
|
+
# @param sseq2 [Array<SimpleSelector or String>]
|
264
|
+
# @return [Boolean]
|
205
265
|
def subweave_superselector?(sseq1, sseq2)
|
206
266
|
if sseq1.size > 1
|
207
267
|
# More complex selectors are never superselectors of less complex ones
|
data/lib/sass/selector/simple.rb
CHANGED
@@ -79,7 +79,7 @@ module Sass
|
|
79
79
|
sels_with_ix = Sass::Util.enum_with_index(sels)
|
80
80
|
_, i =
|
81
81
|
if self.is_a?(Pseudo) || self.is_a?(SelectorPseudoClass)
|
82
|
-
sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) && sels.last.type == :element}
|
82
|
+
sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) && (sels.last.final? || sels.last.type == :element)}
|
83
83
|
else
|
84
84
|
sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) || sel.is_a?(SelectorPseudoClass)}
|
85
85
|
end
|
data/lib/sass/shared.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'strscan'
|
2
|
-
|
3
1
|
module Sass
|
4
2
|
# This module contains functionality that's shared between Haml and Sass.
|
5
3
|
module Shared
|
@@ -16,7 +14,7 @@ module Sass
|
|
16
14
|
# @yieldparam scan [StringScanner] The scanner scanning through the string
|
17
15
|
# @return [String] The text remaining in the scanner after all `#{`s have been processed
|
18
16
|
def handle_interpolation(str)
|
19
|
-
scan =
|
17
|
+
scan = Sass::Util::MultibyteStringScanner.new(str)
|
20
18
|
yield scan while scan.scan(/(.*?)(\\*)\#\{/m)
|
21
19
|
scan.rest
|
22
20
|
end
|
@@ -40,7 +38,7 @@ module Sass
|
|
40
38
|
# `["Foo (Bar (Baz bang) bop)", " (Bang (bop bip))"]` in the example above.
|
41
39
|
def balance(scanner, start, finish, count = 0)
|
42
40
|
str = ''
|
43
|
-
scanner =
|
41
|
+
scanner = Sass::Util::MultibyteStringScanner.new(scanner) unless scanner.is_a? StringScanner
|
44
42
|
regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
|
45
43
|
while scanner.scan(regexp)
|
46
44
|
str << scanner.matched
|
@@ -177,7 +177,8 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
177
177
|
def visit_mixin(node)
|
178
178
|
unless node.args.empty? && node.keywords.empty?
|
179
179
|
args = node.args.map {|a| a.to_sass(@options)}.join(", ")
|
180
|
-
keywords = node.keywords
|
180
|
+
keywords = Sass::Util.hash_to_a(node.keywords).
|
181
|
+
map {|k, v| "$#{dasherize(k)}: #{v.to_sass(@options)}"}.join(', ')
|
181
182
|
arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
|
182
183
|
end
|
183
184
|
"#{tab_str}#{@format == :sass ? '+' : '@include '}#{dasherize(node.name)}#{arglist}#{semi}\n"
|
@@ -52,12 +52,26 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
|
|
52
52
|
def visit_root(node)
|
53
53
|
yield
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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.value =~ /^@import /i
|
66
|
+
end
|
67
|
+
charset_and_index = Sass::Util.ruby1_8? &&
|
68
|
+
node.children.each_with_index.find {|c, _| c.is_a?(Sass::Tree::CharsetNode)}
|
69
|
+
if charset_and_index
|
70
|
+
index = charset_and_index.last
|
71
|
+
node.children = node.children[0..index] + imports + node.children[index+1..-1]
|
72
|
+
else
|
73
|
+
node.children = imports + node.children
|
74
|
+
end
|
61
75
|
end
|
62
76
|
|
63
77
|
return node, @extends
|
@@ -226,6 +226,8 @@ END
|
|
226
226
|
# Runs SassScript interpolation in the selector,
|
227
227
|
# and then parses the result into a {Sass::Selector::CommaSequence}.
|
228
228
|
def visit_rule(node)
|
229
|
+
rule = node.rule
|
230
|
+
rule = rule.map {|e| e.is_a?(String) && e != ' ' ? e.strip : e} if node.style == :compressed
|
229
231
|
parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.filename, node.line)
|
230
232
|
node.parsed_rules ||= parser.parse_selector
|
231
233
|
if node.options[:trace_selectors]
|
@@ -270,6 +272,15 @@ END
|
|
270
272
|
|
271
273
|
def visit_directive(node)
|
272
274
|
if node.value['#{']
|
275
|
+
if node.value =~ /^@import (?!url\()/
|
276
|
+
Sass::Util.sass_warn <<WARNING
|
277
|
+
DEPRECATION WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
|
278
|
+
@import directives using \#{} interpolation will need to use url() in Sass 3.2.
|
279
|
+
For example:
|
280
|
+
|
281
|
+
@import url("http://\#{$url}/style.css");
|
282
|
+
WARNING
|
283
|
+
end
|
273
284
|
node.value = run_interp(Sass::Engine.parse_interp(node.value, node.line, 0, node.options))
|
274
285
|
end
|
275
286
|
yield
|
@@ -67,8 +67,10 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def visit_directive(node)
|
70
|
+
was_in_directive = @in_directive
|
70
71
|
return node.value + ";" unless node.has_children
|
71
72
|
return node.value + " {}" if node.children.empty?
|
73
|
+
@in_directive = @in_directive || !node.is_a?(Sass::Tree::MediaNode)
|
72
74
|
result = if node.style == :compressed
|
73
75
|
"#{node.value}{"
|
74
76
|
else
|
@@ -101,6 +103,8 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
101
103
|
else
|
102
104
|
(node.style == :expanded ? "\n" : " ") + "}\n"
|
103
105
|
end
|
106
|
+
ensure
|
107
|
+
@in_directive = was_in_directive
|
104
108
|
end
|
105
109
|
|
106
110
|
def visit_media(node)
|
@@ -132,7 +136,11 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
132
136
|
|
133
137
|
joined_rules = node.resolved_rules.members.map do |seq|
|
134
138
|
rule_part = seq.to_a.join
|
135
|
-
|
139
|
+
if node.style == :compressed
|
140
|
+
rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
|
141
|
+
rule_part.gsub!(/\s*([,+>])\s*/m, '\1')
|
142
|
+
rule_part.strip!
|
143
|
+
end
|
136
144
|
rule_part
|
137
145
|
end.join(rule_separator)
|
138
146
|
|
@@ -144,7 +152,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
144
152
|
old_spaces = ' ' * @tabs
|
145
153
|
spaces = ' ' * (@tabs + 1)
|
146
154
|
if node.style != :compressed
|
147
|
-
if node.options[:debug_info]
|
155
|
+
if node.options[:debug_info] && !@in_directive
|
148
156
|
to_return << visit(debug_info_rule(node.debug_info, node.options)) << "\n"
|
149
157
|
elsif node.options[:trace_selectors]
|
150
158
|
to_return << "#{old_spaces}/* "
|
@@ -190,7 +198,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
190
198
|
|
191
199
|
def debug_info_rule(debug_info, options)
|
192
200
|
node = Sass::Tree::DirectiveNode.new("@media -sass-debug-info")
|
193
|
-
debug_info.map {|k, v| [k.to_s, v.to_s]}.
|
201
|
+
Sass::Util.hash_to_a(debug_info.map {|k, v| [k.to_s, v.to_s]}).each do |k, v|
|
194
202
|
rule = Sass::Tree::RuleNode.new([""])
|
195
203
|
rule.resolved_rules = Sass::Selector::CommaSequence.new(
|
196
204
|
[Sass::Selector::Sequence.new(
|
data/lib/sass/util.rb
CHANGED
@@ -2,7 +2,6 @@ require 'erb'
|
|
2
2
|
require 'set'
|
3
3
|
require 'enumerator'
|
4
4
|
require 'stringio'
|
5
|
-
require 'strscan'
|
6
5
|
require 'rbconfig'
|
7
6
|
|
8
7
|
require 'sass/root'
|
@@ -219,6 +218,20 @@ module Sass
|
|
219
218
|
lcs_backtrace(lcs_table(x, y, &block), x, y, x.size-1, y.size-1, &block)
|
220
219
|
end
|
221
220
|
|
221
|
+
# Converts a Hash to an Array. This is usually identical to `Hash#to_a`,
|
222
|
+
# with the following exceptions:
|
223
|
+
#
|
224
|
+
# * In Ruby 1.8, `Hash#to_a` is not deterministically ordered, but this is.
|
225
|
+
# * In Ruby 1.9 when running tests, this is ordered in the same way it would
|
226
|
+
# be under Ruby 1.8 (sorted key order rather than insertion order).
|
227
|
+
#
|
228
|
+
# @param hash [Hash]
|
229
|
+
# @return [Array]
|
230
|
+
def hash_to_a(hash)
|
231
|
+
return has.to_a unless ruby1_8? || defined?(Test::Unit)
|
232
|
+
return hash.sort_by {|k, v| k}
|
233
|
+
end
|
234
|
+
|
222
235
|
# Returns information about the caller of the previous method.
|
223
236
|
#
|
224
237
|
# @param entry [String] An entry in the `#caller` list, or a similarly formatted string
|
@@ -562,6 +575,24 @@ MSG
|
|
562
575
|
ruby1_8? ? enum.enum_slice(n) : enum.each_slice(n)
|
563
576
|
end
|
564
577
|
|
578
|
+
# Destructively removes all elements from an array that match a block, and
|
579
|
+
# returns the removed elements.
|
580
|
+
#
|
581
|
+
# @param array [Array] The array from which to remove elements.
|
582
|
+
# @yield [el] Called for each element.
|
583
|
+
# @yieldparam el [*] The element to test.
|
584
|
+
# @yieldreturn [Boolean] Whether or not to extract the element.
|
585
|
+
# @return [Array] The extracted elements.
|
586
|
+
def extract!(array)
|
587
|
+
out = []
|
588
|
+
array.reject! do |e|
|
589
|
+
next false unless yield e
|
590
|
+
out << e
|
591
|
+
true
|
592
|
+
end
|
593
|
+
out
|
594
|
+
end
|
595
|
+
|
565
596
|
# Returns the ASCII code of the given character.
|
566
597
|
#
|
567
598
|
# @param c [String] All characters but the first are ignored.
|
@@ -719,3 +750,5 @@ MSG
|
|
719
750
|
end
|
720
751
|
end
|
721
752
|
end
|
753
|
+
|
754
|
+
require 'sass/util/multibyte_string_scanner'
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
if Sass::Util.ruby1_8?
|
4
|
+
Sass::Util::MultibyteStringScanner = StringScanner
|
5
|
+
else
|
6
|
+
# A wrapper of the native StringScanner class that works correctly with
|
7
|
+
# multibyte character encodings. The native class deals only in bytes, not
|
8
|
+
# characters, for methods like [#pos] and [#matched_size]. This class deals
|
9
|
+
# only in characters, instead.
|
10
|
+
class Sass::Util::MultibyteStringScanner < StringScanner
|
11
|
+
def self.new(str)
|
12
|
+
return StringScanner.new(str) if str.ascii_only?
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(str)
|
17
|
+
super
|
18
|
+
@mb_pos = 0
|
19
|
+
@mb_matched_size = nil
|
20
|
+
@mb_last_pos = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :byte_pos, :pos
|
24
|
+
alias_method :byte_matched_size, :matched_size
|
25
|
+
|
26
|
+
def check(pattern); _match super; end
|
27
|
+
def check_until(pattern); _matched super; end
|
28
|
+
def getch; _forward _match super; end
|
29
|
+
def match?(pattern); _size check(pattern); end
|
30
|
+
def matched_size; @mb_matched_size; end
|
31
|
+
def peek(len); string[@mb_pos, len]; end
|
32
|
+
alias_method :peep, :peek
|
33
|
+
def pos; @mb_pos; end
|
34
|
+
alias_method :pointer, :pos
|
35
|
+
def rest_size; rest.size; end
|
36
|
+
def scan(pattern); _forward _match super; end
|
37
|
+
def scan_until(pattern); _forward _matched super; end
|
38
|
+
def skip(pattern); _size scan(pattern); end
|
39
|
+
def skip_until(pattern); _matched _size scan_until(pattern); end
|
40
|
+
|
41
|
+
def get_byte
|
42
|
+
raise "MultibyteStringScanner doesn't support #get_byte."
|
43
|
+
end
|
44
|
+
|
45
|
+
def getbyte
|
46
|
+
raise "MultibyteStringScanner doesn't support #getbyte."
|
47
|
+
end
|
48
|
+
|
49
|
+
def pos=(n)
|
50
|
+
@mb_last_pos = nil
|
51
|
+
|
52
|
+
# We set position kind of a lot during parsing, so we want it to be as
|
53
|
+
# efficient as possible. This is complicated by the fact that UTF-8 is a
|
54
|
+
# variable-length encoding, so it's difficult to find the byte length that
|
55
|
+
# corresponds to a given character length.
|
56
|
+
#
|
57
|
+
# Our heuristic here is to try to count the fewest possible characters. So
|
58
|
+
# if the new position is close to the current one, just count the
|
59
|
+
# characters between the two; if the new position is closer to the
|
60
|
+
# beginning of the string, just count the characters from there.
|
61
|
+
if @mb_pos - n < @mb_pos / 2
|
62
|
+
# New position is close to old position
|
63
|
+
byte_delta = @mb_pos > n ? -string[n...@mb_pos].bytesize : string[@mb_pos...n].bytesize
|
64
|
+
super(byte_pos + byte_delta)
|
65
|
+
else
|
66
|
+
# New position is close to BOS
|
67
|
+
super(string[0...n].bytesize)
|
68
|
+
end
|
69
|
+
@mb_pos = n
|
70
|
+
end
|
71
|
+
|
72
|
+
def reset
|
73
|
+
@mb_pos = 0
|
74
|
+
@mb_matched_size = nil
|
75
|
+
@mb_last_pos = nil
|
76
|
+
super
|
77
|
+
end
|
78
|
+
|
79
|
+
def scan_full(pattern, advance_pointer_p, return_string_p)
|
80
|
+
res = _match super(pattern, advance_pointer_p, true)
|
81
|
+
_forward res if advance_pointer_p
|
82
|
+
return res if return_string_p
|
83
|
+
end
|
84
|
+
|
85
|
+
def search_full(pattern, advance_pointer_p, return_string_p)
|
86
|
+
res = super(pattern, advance_pointer_p, true)
|
87
|
+
_forward res if advance_pointer_p
|
88
|
+
_matched((res if return_string_p))
|
89
|
+
end
|
90
|
+
|
91
|
+
def string=(str)
|
92
|
+
@mb_pos = 0
|
93
|
+
@mb_matched_size = nil
|
94
|
+
@mb_last_pos = nil
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def terminate
|
99
|
+
@mb_pos = string.size
|
100
|
+
@mb_matched_size = nil
|
101
|
+
@mb_last_pos = nil
|
102
|
+
super
|
103
|
+
end
|
104
|
+
alias_method :clear, :terminate
|
105
|
+
|
106
|
+
def unscan
|
107
|
+
super
|
108
|
+
@mb_pos = @mb_last_pos
|
109
|
+
@mb_last_pos = @mb_matched_size = nil
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def _size(str)
|
115
|
+
str && str.size
|
116
|
+
end
|
117
|
+
|
118
|
+
def _match(str)
|
119
|
+
@mb_matched_size = str && str.size
|
120
|
+
str
|
121
|
+
end
|
122
|
+
|
123
|
+
def _matched(res)
|
124
|
+
_match matched
|
125
|
+
res
|
126
|
+
end
|
127
|
+
|
128
|
+
def _forward(str)
|
129
|
+
@mb_last_pos = @mb_pos
|
130
|
+
@mb_pos += str.size if str
|
131
|
+
str
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
data/test/sass/engine_test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# -*- coding: utf-8 -*-
|
3
|
-
require 'test_helper'
|
4
|
-
require '
|
3
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
4
|
+
require File.dirname(__FILE__) + '/test_helper'
|
5
5
|
require 'sass/engine'
|
6
6
|
require 'stringio'
|
7
7
|
require 'mock_importer'
|
@@ -608,13 +608,21 @@ CSS
|
|
608
608
|
end
|
609
609
|
|
610
610
|
def test_http_import
|
611
|
-
assert_equal("@import
|
611
|
+
assert_equal("@import \"http://fonts.googleapis.com/css?family=Droid+Sans\";\n",
|
612
612
|
render("@import \"http://fonts.googleapis.com/css?family=Droid+Sans\""))
|
613
613
|
end
|
614
614
|
|
615
|
-
def
|
616
|
-
|
617
|
-
|
615
|
+
def test_import_with_interpolation
|
616
|
+
assert_warning(<<WARNING) do
|
617
|
+
DEPRECATION WARNING on line 2 of test_import_with_interpolation_inline.sass:
|
618
|
+
@import directives using \#{} interpolation will need to use url() in Sass 3.2.
|
619
|
+
For example:
|
620
|
+
|
621
|
+
@import url("http://\#{$url}/style.css");
|
622
|
+
WARNING
|
623
|
+
assert_equal("@import \"http://fonts.googleapis.com/css?family=Droid+Sans\";\n",
|
624
|
+
render("$family: unquote(\"Droid+Sans\")\n@import \"http://fonts.googleapis.com/css?family=\#{$family}\"\n"))
|
625
|
+
end
|
618
626
|
assert_equal("@import url(\"http://fonts.googleapis.com/css?family=Droid+Sans\");\n",
|
619
627
|
render("$family: unquote(\"Droid+Sans\")\n@import url(\"http://fonts.googleapis.com/css?family=\#{$family}\")\n"))
|
620
628
|
end
|
@@ -1006,6 +1014,23 @@ foo
|
|
1006
1014
|
SASS
|
1007
1015
|
end
|
1008
1016
|
|
1017
|
+
def test_debug_info_in_keyframes
|
1018
|
+
assert_equal(<<CSS, render(<<SASS, :debug_info => true))
|
1019
|
+
@-webkit-keyframes warm {
|
1020
|
+
from {
|
1021
|
+
color: black; }
|
1022
|
+
|
1023
|
+
to {
|
1024
|
+
color: red; } }
|
1025
|
+
CSS
|
1026
|
+
@-webkit-keyframes warm
|
1027
|
+
from
|
1028
|
+
color: black
|
1029
|
+
to
|
1030
|
+
color: red
|
1031
|
+
SASS
|
1032
|
+
end
|
1033
|
+
|
1009
1034
|
def test_empty_first_line
|
1010
1035
|
assert_equal("#a {\n b: c; }\n", render("#a\n\n b: c"))
|
1011
1036
|
end
|
@@ -2448,6 +2473,15 @@ CSS
|
|
2448
2473
|
SASS
|
2449
2474
|
end
|
2450
2475
|
|
2476
|
+
def test_selector_compression
|
2477
|
+
assert_equal <<CSS, render(<<SASS, :style => :compressed)
|
2478
|
+
a>b,c+d,:-moz-any(e,f,g){h:i}
|
2479
|
+
CSS
|
2480
|
+
a > b, c + d, :-moz-any(e, f, g)
|
2481
|
+
h: i
|
2482
|
+
SASS
|
2483
|
+
end
|
2484
|
+
|
2451
2485
|
# Encodings
|
2452
2486
|
|
2453
2487
|
unless Sass::Util.ruby1_8?
|
@@ -2560,6 +2594,31 @@ CSS
|
|
2560
2594
|
a: b
|
2561
2595
|
SASS
|
2562
2596
|
end
|
2597
|
+
|
2598
|
+
# Encoding Regression Test
|
2599
|
+
|
2600
|
+
def test_multibyte_prop_name
|
2601
|
+
assert_equal(<<CSS, render(<<SASS))
|
2602
|
+
@charset "UTF-8";
|
2603
|
+
#bar {
|
2604
|
+
cölor: blue; }
|
2605
|
+
CSS
|
2606
|
+
#bar
|
2607
|
+
cölor: blue
|
2608
|
+
SASS
|
2609
|
+
end
|
2610
|
+
|
2611
|
+
def test_multibyte_and_interpolation
|
2612
|
+
assert_equal(<<CSS, render(<<SCSS, :syntax => :scss))
|
2613
|
+
#bar {
|
2614
|
+
background: a 0%; }
|
2615
|
+
CSS
|
2616
|
+
#bar {
|
2617
|
+
//
|
2618
|
+
background: \#{a} 0%;
|
2619
|
+
}
|
2620
|
+
SCSS
|
2621
|
+
end
|
2563
2622
|
end
|
2564
2623
|
|
2565
2624
|
def test_original_filename_set
|
data/test/sass/extend_test.rb
CHANGED
@@ -741,6 +741,22 @@ SCSS
|
|
741
741
|
CSS
|
742
742
|
:foo.baz {a: b}
|
743
743
|
:bar {@extend .baz}
|
744
|
+
SCSS
|
745
|
+
|
746
|
+
assert_equal <<CSS, render(<<SCSS)
|
747
|
+
.baz:foo, :foo:after {
|
748
|
+
a: b; }
|
749
|
+
CSS
|
750
|
+
.baz:foo {a: b}
|
751
|
+
:after {@extend .baz}
|
752
|
+
SCSS
|
753
|
+
|
754
|
+
assert_equal <<CSS, render(<<SCSS)
|
755
|
+
.baz:after, :foo:after {
|
756
|
+
a: b; }
|
757
|
+
CSS
|
758
|
+
.baz:after {a: b}
|
759
|
+
:foo {@extend .baz}
|
744
760
|
SCSS
|
745
761
|
|
746
762
|
assert_equal <<CSS, render(<<SCSS)
|
@@ -1339,6 +1355,78 @@ CSS
|
|
1339
1355
|
SCSS
|
1340
1356
|
end
|
1341
1357
|
|
1358
|
+
# Regression Tests
|
1359
|
+
|
1360
|
+
def test_duplicated_selector_with_newlines
|
1361
|
+
assert_equal(<<CSS, render(<<SCSS))
|
1362
|
+
.example-1-1,
|
1363
|
+
.example-1-2,
|
1364
|
+
.my-page-1 .my-module-1-1,
|
1365
|
+
.example-1-3 {
|
1366
|
+
a: b; }
|
1367
|
+
CSS
|
1368
|
+
.example-1-1,
|
1369
|
+
.example-1-2,
|
1370
|
+
.example-1-3 {
|
1371
|
+
a: b;
|
1372
|
+
}
|
1373
|
+
|
1374
|
+
.my-page-1 .my-module-1-1 {@extend .example-1-2}
|
1375
|
+
SCSS
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
def test_nested_selector_with_child_selector_hack_extendee
|
1379
|
+
assert_equal <<CSS, render(<<SCSS)
|
1380
|
+
> .foo, > foo bar {
|
1381
|
+
a: b; }
|
1382
|
+
CSS
|
1383
|
+
> .foo {a: b}
|
1384
|
+
foo bar {@extend .foo}
|
1385
|
+
SCSS
|
1386
|
+
end
|
1387
|
+
|
1388
|
+
def test_nested_selector_with_child_selector_hack_extender
|
1389
|
+
assert_equal <<CSS, render(<<SCSS)
|
1390
|
+
.foo .bar, > .foo foo bar, > foo .foo bar {
|
1391
|
+
a: b; }
|
1392
|
+
CSS
|
1393
|
+
.foo .bar {a: b}
|
1394
|
+
> foo bar {@extend .bar}
|
1395
|
+
SCSS
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
def test_nested_selector_with_child_selector_hack_extender_and_extendee
|
1399
|
+
assert_equal <<CSS, render(<<SCSS)
|
1400
|
+
> .foo, > foo bar {
|
1401
|
+
a: b; }
|
1402
|
+
CSS
|
1403
|
+
> .foo {a: b}
|
1404
|
+
> foo bar {@extend .foo}
|
1405
|
+
SCSS
|
1406
|
+
end
|
1407
|
+
|
1408
|
+
def test_nested_selector_with_child_selector_hack_extender_and_sibling_selector_extendee
|
1409
|
+
assert_equal <<CSS, render(<<SCSS)
|
1410
|
+
~ .foo {
|
1411
|
+
a: b; }
|
1412
|
+
CSS
|
1413
|
+
~ .foo {a: b}
|
1414
|
+
> foo bar {@extend .foo}
|
1415
|
+
SCSS
|
1416
|
+
end
|
1417
|
+
|
1418
|
+
def test_nested_selector_with_child_selector_hack_extender_and_extendee_and_newline
|
1419
|
+
assert_equal <<CSS, render(<<SCSS)
|
1420
|
+
> .foo, > flip,
|
1421
|
+
> foo bar {
|
1422
|
+
a: b; }
|
1423
|
+
CSS
|
1424
|
+
> .foo {a: b}
|
1425
|
+
flip,
|
1426
|
+
> foo bar {@extend .foo}
|
1427
|
+
SCSS
|
1428
|
+
end
|
1429
|
+
|
1342
1430
|
private
|
1343
1431
|
|
1344
1432
|
def render(sass, options = {})
|
@@ -1,3 +1,5 @@
|
|
1
|
+
@import url(basic.css);
|
2
|
+
@import url(../results/complex.css);
|
1
3
|
imported { otherconst: hello; myconst: goodbye; pre-mixin: here; }
|
2
4
|
|
3
5
|
body { font: Arial; background: blue; }
|
@@ -22,8 +24,6 @@ body { font: Arial; background: blue; }
|
|
22
24
|
#content.user.show #container.top #column.right { width: 600px; }
|
23
25
|
#content.user.show #container.bottom { background: brown; }
|
24
26
|
|
25
|
-
@import url(basic.css);
|
26
|
-
@import url(../results/complex.css);
|
27
27
|
#foo { background-color: #bbaaff; }
|
28
28
|
|
29
29
|
nonimported { myconst: hello; otherconst: goodbye; post-mixin: here; }
|
@@ -1,3 +1,5 @@
|
|
1
|
+
@import url(basic.css);
|
2
|
+
@import url(../results/complex.css);
|
1
3
|
imported { otherconst: hello; myconst: goodbye; pre-mixin: here; }
|
2
4
|
|
3
5
|
body { font: Arial; background: blue; }
|
@@ -24,8 +26,6 @@ body { font: Arial; background: blue; }
|
|
24
26
|
#content.user.show #container.top #column.right { width: 600px; }
|
25
27
|
#content.user.show #container.bottom { background: brown; }
|
26
28
|
|
27
|
-
@import url(basic.css);
|
28
|
-
@import url(../results/complex.css);
|
29
29
|
#foo { background-color: #bbaaff; }
|
30
30
|
|
31
31
|
nonimported { myconst: hello; otherconst: goodbye; post-mixin: here; }
|
@@ -1,3 +1,5 @@
|
|
1
|
+
@import url(basic.css);
|
2
|
+
@import url(../results/complex.css);
|
1
3
|
imported { otherconst: hello; myconst: goodbye; pre-mixin: here; }
|
2
4
|
|
3
5
|
body { font: Arial; background: blue; }
|
@@ -24,8 +26,6 @@ body { font: Arial; background: blue; }
|
|
24
26
|
#content.user.show #container.top #column.right { width: 600px; }
|
25
27
|
#content.user.show #container.bottom { background: brown; }
|
26
28
|
|
27
|
-
@import url(basic.css);
|
28
|
-
@import url(../results/complex.css);
|
29
29
|
#foo { background-color: #bbaaff; }
|
30
30
|
|
31
31
|
nonimported { myconst: hello; otherconst: goodbye; post-mixin: here; }
|
data/test/sass/script_test.rb
CHANGED
@@ -432,6 +432,11 @@ SASS
|
|
432
432
|
assert_raise_message(Sass::SyntaxError, "wrong number of arguments (1 for 0) for `arg-error'") {resolve("arg-error(1)")}
|
433
433
|
end
|
434
434
|
|
435
|
+
def test_boolean_ops_short_circuit
|
436
|
+
assert_equal "false", resolve("$ie and $ie <= 7", {}, env('ie' => Sass::Script::Bool.new(false)))
|
437
|
+
assert_equal "true", resolve("$ie or $undef", {}, env('ie' => Sass::Script::Bool.new(true)))
|
438
|
+
end
|
439
|
+
|
435
440
|
# Regression Tests
|
436
441
|
|
437
442
|
def test_funcall_has_higher_precedence_than_color_name
|
data/test/sass/scss/scss_test.rb
CHANGED
@@ -1224,11 +1224,11 @@ SCSS
|
|
1224
1224
|
|
1225
1225
|
|
1226
1226
|
def test_newlines_removed_from_selectors_when_compressed
|
1227
|
-
assert_equal <<CSS, render(<<SCSS, :style
|
1227
|
+
assert_equal <<CSS, render(<<SCSS, :style => :compressed)
|
1228
1228
|
z a,z b{display:block}
|
1229
1229
|
CSS
|
1230
|
-
a
|
1231
|
-
b {
|
1230
|
+
a
|
1231
|
+
, b {
|
1232
1232
|
z & {
|
1233
1233
|
display: block;
|
1234
1234
|
}
|
@@ -0,0 +1,147 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
require File.dirname(__FILE__) + '/../../test_helper'
|
4
|
+
|
5
|
+
unless Sass::Util.ruby1_8?
|
6
|
+
class MultibyteStringScannerTest < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
@scanner = Sass::Util::MultibyteStringScanner.new("cölorfül")
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_initial
|
12
|
+
assert_scanner_state 0, 0, nil, nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_check
|
16
|
+
assert_equal 'cö', @scanner.check(/../)
|
17
|
+
assert_scanner_state 0, 0, 2, 3
|
18
|
+
assert_equal 0, @scanner.pos
|
19
|
+
assert_equal 0, @scanner.pos
|
20
|
+
assert_equal 2, @scanner.matched_size
|
21
|
+
assert_equal 3, @scanner.byte_matched_size
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_check_until
|
25
|
+
assert_equal 'cölorfü', @scanner.check_until(/f./)
|
26
|
+
assert_scanner_state 0, 0, 2, 3
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_getch
|
30
|
+
assert_equal 'c', @scanner.getch
|
31
|
+
assert_equal 'ö', @scanner.getch
|
32
|
+
assert_scanner_state 2, 3, 1, 2
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_match?
|
36
|
+
assert_equal 2, @scanner.match?(/../)
|
37
|
+
assert_scanner_state 0, 0, 2, 3
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_peek
|
41
|
+
assert_equal 'cö', @scanner.peek(2)
|
42
|
+
assert_scanner_state 0, 0, nil, nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_rest_size
|
46
|
+
assert_equal 'cö', @scanner.scan(/../)
|
47
|
+
assert_equal 6, @scanner.rest_size
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_scan
|
51
|
+
assert_equal 'cö', @scanner.scan(/../)
|
52
|
+
assert_scanner_state 2, 3, 2, 3
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_scan_until
|
56
|
+
assert_equal 'cölorfü', @scanner.scan_until(/f./)
|
57
|
+
assert_scanner_state 7, 9, 2, 3
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_skip
|
61
|
+
assert_equal 2, @scanner.skip(/../)
|
62
|
+
assert_scanner_state 2, 3, 2, 3
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_skip_until
|
66
|
+
assert_equal 7, @scanner.skip_until(/f./)
|
67
|
+
assert_scanner_state 7, 9, 2, 3
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_set_pos
|
71
|
+
@scanner.pos = 7
|
72
|
+
assert_scanner_state 7, 9, nil, nil
|
73
|
+
@scanner.pos = 6
|
74
|
+
assert_scanner_state 6, 7, nil, nil
|
75
|
+
@scanner.pos = 1
|
76
|
+
assert_scanner_state 1, 1, nil, nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_reset
|
80
|
+
@scanner.scan(/../)
|
81
|
+
@scanner.reset
|
82
|
+
assert_scanner_state 0, 0, nil, nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_scan_full
|
86
|
+
assert_equal 'cö', @scanner.scan_full(/../, true, true)
|
87
|
+
assert_scanner_state 2, 3, 2, 3
|
88
|
+
|
89
|
+
@scanner.reset
|
90
|
+
assert_equal 'cö', @scanner.scan_full(/../, false, true)
|
91
|
+
assert_scanner_state 0, 0, 2, 3
|
92
|
+
|
93
|
+
@scanner.reset
|
94
|
+
assert_nil @scanner.scan_full(/../, true, false)
|
95
|
+
assert_scanner_state 2, 3, 2, 3
|
96
|
+
|
97
|
+
@scanner.reset
|
98
|
+
assert_nil @scanner.scan_full(/../, false, false)
|
99
|
+
assert_scanner_state 0, 0, 2, 3
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_search_full
|
103
|
+
assert_equal 'cölorfü', @scanner.search_full(/f./, true, true)
|
104
|
+
assert_scanner_state 7, 9, 2, 3
|
105
|
+
|
106
|
+
@scanner.reset
|
107
|
+
assert_equal 'cölorfü', @scanner.search_full(/f./, false, true)
|
108
|
+
assert_scanner_state 0, 0, 2, 3
|
109
|
+
|
110
|
+
@scanner.reset
|
111
|
+
assert_nil @scanner.search_full(/f./, true, false)
|
112
|
+
assert_scanner_state 7, 9, 2, 3
|
113
|
+
|
114
|
+
@scanner.reset
|
115
|
+
assert_nil @scanner.search_full(/f./, false, false)
|
116
|
+
assert_scanner_state 0, 0, 2, 3
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_set_string
|
120
|
+
@scanner.scan(/../)
|
121
|
+
@scanner.string = 'föóbâr'
|
122
|
+
assert_scanner_state 0, 0, nil, nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_terminate
|
126
|
+
@scanner.scan(/../)
|
127
|
+
@scanner.terminate
|
128
|
+
assert_scanner_state 8, 10, nil, nil
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_unscan
|
132
|
+
@scanner.scan(/../)
|
133
|
+
@scanner.scan_until(/f./)
|
134
|
+
@scanner.unscan
|
135
|
+
assert_scanner_state 2, 3, nil, nil
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def assert_scanner_state(pos, byte_pos, matched_size, byte_matched_size)
|
141
|
+
assert_equal pos, @scanner.pos, 'pos'
|
142
|
+
assert_equal byte_pos, @scanner.byte_pos, 'byte_pos'
|
143
|
+
assert_equal matched_size, @scanner.matched_size, 'matched_size'
|
144
|
+
assert_equal byte_matched_size, @scanner.byte_matched_size, 'byte_matched_size'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/test/sass/util_test.rb
CHANGED
@@ -159,6 +159,12 @@ class UtilTest < Test::Unit::TestCase
|
|
159
159
|
enum_cons(%w[foo bar baz], 2).map {|s1, s2| "#{s1}#{s2}"})
|
160
160
|
end
|
161
161
|
|
162
|
+
def test_extract
|
163
|
+
arr = [1, 2, 3, 4, 5]
|
164
|
+
assert_equal([1, 3, 5], extract!(arr) {|e| e % 2 == 1})
|
165
|
+
assert_equal([2, 4], arr)
|
166
|
+
end
|
167
|
+
|
162
168
|
def test_ord
|
163
169
|
assert_equal(102, ord("f"))
|
164
170
|
assert_equal(98, ord("bar"))
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sass
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 3
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 3.1.
|
9
|
+
- 13
|
10
|
+
version: 3.1.13
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Nathan Weizenbaum
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date:
|
20
|
+
date: 2012-02-03 00:00:00 -08:00
|
21
21
|
default_executable:
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|
@@ -116,6 +116,7 @@ files:
|
|
116
116
|
- lib/sass/root.rb
|
117
117
|
- lib/sass/importers.rb
|
118
118
|
- lib/sass/logger.rb
|
119
|
+
- lib/sass/util/multibyte_string_scanner.rb
|
119
120
|
- lib/sass/util/subset_map.rb
|
120
121
|
- lib/sass/scss.rb
|
121
122
|
- lib/sass/scss/static_parser.rb
|
@@ -257,6 +258,7 @@ files:
|
|
257
258
|
- test/sass/conversion_test.rb
|
258
259
|
- test/sass/script_test.rb
|
259
260
|
- test/sass/util/subset_map_test.rb
|
261
|
+
- test/sass/util/multibyte_string_scanner_test.rb
|
260
262
|
- test/sass/callbacks_test.rb
|
261
263
|
- test/sass/importer_test.rb
|
262
264
|
- test/sass/scss/css_test.rb
|
@@ -356,6 +358,7 @@ test_files:
|
|
356
358
|
- test/sass/conversion_test.rb
|
357
359
|
- test/sass/script_test.rb
|
358
360
|
- test/sass/util/subset_map_test.rb
|
361
|
+
- test/sass/util/multibyte_string_scanner_test.rb
|
359
362
|
- test/sass/callbacks_test.rb
|
360
363
|
- test/sass/importer_test.rb
|
361
364
|
- test/sass/scss/css_test.rb
|