sass 3.2.0.alpha.57 → 3.2.0.alpha.59

Sign up to get free protection for your applications and to get access to all the features.
@@ -130,6 +130,27 @@ module Sass
130
130
  raise e
131
131
  end
132
132
 
133
+ # Parse a single string value, possibly containing interpolation.
134
+ # Doesn't assert that the scanner is finished after parsing.
135
+ #
136
+ # @return [Script::Node] The root node of the parse tree.
137
+ # @raise [Sass::SyntaxError] if the string isn't valid SassScript
138
+ def parse_string
139
+ unless (peek = @lexer.peek) &&
140
+ (peek.type == :string ||
141
+ (peek.type == :funcall && peek.value.downcase == 'url'))
142
+ lexer.expected!("string")
143
+ end
144
+
145
+ expr = assert_expr :funcall
146
+ expr.options = @options
147
+ @lexer.unpeek!
148
+ expr
149
+ rescue Sass::SyntaxError => e
150
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
151
+ raise e
152
+ end
153
+
133
154
  # Parses a SassScript expression.
134
155
  #
135
156
  # @overload parse(str, line, offset, filename = nil)
@@ -7,22 +7,11 @@ module Sass
7
7
  # parent references, nested selectors, and so forth.
8
8
  # It does support all the same CSS hacks as the SCSS parser, though.
9
9
  class CssParser < StaticParser
10
- # Parse a selector, and return its value as a string.
11
- #
12
- # @return [String, nil] The parsed selector, or nil if no selector was parsed
13
- # @raise [Sass::SyntaxError] if there's a syntax error in the selector
14
- def parse_selector_string
15
- init_scanner!
16
- str {return unless selector}
17
- end
18
-
19
10
  private
20
11
 
21
12
  def placeholder_selector; nil; end
22
13
  def parent_selector; nil; end
23
14
  def interpolation; nil; end
24
- def interp_string; tok(STRING); end
25
- def interp_ident(ident = IDENT); tok(ident); end
26
15
  def use_css_import?; true; end
27
16
 
28
17
  def block_child(context)
@@ -40,6 +40,18 @@ module Sass
40
40
  interp_ident
41
41
  end
42
42
 
43
+ # Parses a media query list.
44
+ #
45
+ # @return [Sass::Media::QueryList] The parsed query list
46
+ # @raise [Sass::SyntaxError] if there's a syntax error in the query list,
47
+ # or if it doesn't take up the entire input string.
48
+ def parse_media_query_list
49
+ init_scanner!
50
+ ql = media_query_list
51
+ expected("media query list") unless @scanner.eos?
52
+ ql
53
+ end
54
+
43
55
  private
44
56
 
45
57
  include Sass::SCSS::RX
@@ -122,10 +134,11 @@ module Sass
122
134
  end
123
135
 
124
136
  # Most at-rules take expressions (e.g. @import),
125
- # but some (e.g. @page) take selector-like arguments
126
- val = str {break unless expr}
127
- val ||= CssParser.new(@scanner, @line).parse_selector_string
128
- node = node(Sass::Tree::DirectiveNode.new("@#{name} #{val}".strip))
137
+ # but some (e.g. @page) take selector-like arguments.
138
+ # Some take no arguments at all.
139
+ val = expr || selector
140
+ val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
141
+ node = node(Sass::Tree::DirectiveNode.new(val))
129
142
 
130
143
  if tok(/\{/)
131
144
  node.has_children = true
@@ -277,14 +290,21 @@ module Sass
277
290
  end
278
291
 
279
292
  def import_arg
280
- return unless arg = tok(STRING) || (uri = tok!(URI))
281
- path = @scanner[1] || @scanner[2] || @scanner[3]
293
+ return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
294
+ if uri
295
+ str = sass_script(:parse_string)
296
+ media = str {media_query_list}.strip
297
+ media = " #{media}" unless media.empty?
298
+ ss
299
+ return node(Tree::DirectiveNode.new(["@import ", str, media]))
300
+ end
301
+
302
+ path = @scanner[1] || @scanner[2]
282
303
  ss
283
304
 
284
305
  media = str {media_query_list}.strip
285
-
286
- if uri || path =~ /^http:\/\// || !media.strip.empty? || use_css_import?
287
- return node(Sass::Tree::DirectiveNode.new("@import #{arg} #{media}".strip))
306
+ if path =~ /^http:\/\// || !media.empty? || use_css_import?
307
+ return node(Sass::Tree::DirectiveNode.new(["@import #{str} #{media}"]))
288
308
  end
289
309
 
290
310
  node(Sass::Tree::ImportNode.new(path.strip))
@@ -298,52 +318,66 @@ module Sass
298
318
 
299
319
  # http://www.w3.org/TR/css3-mediaqueries/#syntax
300
320
  def media_query_list
301
- has_q = false
302
- q = str {has_q = media_query}
303
-
304
- return unless has_q
305
- queries = [q.strip]
321
+ return unless query = media_query
322
+ queries = [query]
306
323
 
307
324
  ss
308
325
  while tok(/,/)
309
- ss; queries << str {expr!(:media_query)}.strip; ss
326
+ ss; queries << expr!(:media_query)
310
327
  end
328
+ ss
311
329
 
312
- queries
330
+ Sass::Media::QueryList.new(queries)
313
331
  end
314
332
 
315
333
  def media_query
316
- if tok(/only|not/i)
334
+ if ident1 = interp_ident_or_var
317
335
  ss
318
- @expected = "media type (e.g. print, screen)"
319
- tok!(IDENT)
336
+ ident2 = interp_ident_or_var
320
337
  ss
321
- elsif !tok(IDENT) && !media_expr
322
- return
338
+ if ident2 && ident2.length == 1 && ident2[0].is_a?(String) && ident2[0].downcase == 'and'
339
+ query = Sass::Media::Query.new([], ident1, [])
340
+ else
341
+ if ident2
342
+ query = Sass::Media::Query.new(ident1, ident2, [])
343
+ else
344
+ query = Sass::Media::Query.new([], ident1, [])
345
+ end
346
+ return query unless tok(/and/i)
347
+ ss
348
+ end
349
+ end
350
+
351
+ if query
352
+ expr = expr!(:media_expr)
353
+ else
354
+ return unless expr = media_expr
323
355
  end
356
+ query ||= Sass::Media::Query.new([], [], [])
357
+ query.expressions << expr
324
358
 
325
359
  ss
326
360
  while tok(/and/i)
327
- ss; expr!(:media_expr); ss
361
+ ss; query.expressions << expr!(:media_expr)
328
362
  end
329
363
 
330
- true
364
+ query
331
365
  end
332
366
 
333
367
  def media_expr
334
368
  return unless tok(/\(/)
335
369
  ss
336
370
  @expected = "media feature (e.g. min-device-width, color)"
337
- tok!(IDENT)
371
+ name = expr!(:interp_ident_or_var)
338
372
  ss
339
373
 
340
374
  if tok(/:/)
341
- ss; expr!(:expr)
375
+ ss; value = expr!(:expr)
342
376
  end
343
377
  tok!(/\)/)
344
378
  ss
345
379
 
346
- true
380
+ Sass::Media::Expression.new(name, value || [])
347
381
  end
348
382
 
349
383
  def charset_directive
@@ -371,10 +405,6 @@ module Sass
371
405
  str {ss if tok(/[\/,:.=]/)}
372
406
  end
373
407
 
374
- def unary_operator
375
- tok(/[+-]/)
376
- end
377
-
378
408
  def ruleset
379
409
  return unless rules = selector_sequence
380
410
  block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
@@ -507,9 +537,9 @@ module Sass
507
537
 
508
538
  def simple_selector_sequence
509
539
  # This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
510
- return expr unless e = element_name || id_selector || class_selector ||
511
- placeholder_selector || attrib || negation || pseudo || parent_selector ||
512
- interpolation_selector
540
+ return expr(!:allow_var) unless e = element_name || id_selector ||
541
+ class_selector || placeholder_selector || attrib || negation ||
542
+ pseudo || parent_selector || interpolation_selector
513
543
  res = [e]
514
544
 
515
545
  # The tok(/\*/) allows the "E*" hack
@@ -698,17 +728,6 @@ module Sass
698
728
  return space, sass_script(:parse)
699
729
  end
700
730
 
701
- def plain_value
702
- return unless tok(/:/)
703
- space = !str {ss}.empty?
704
- @use_property_exception ||= space || !tok?(IDENT)
705
-
706
- expression = expr
707
- expression << tok(IMPORTANT) if expression
708
- # expression, space, value
709
- return expression, space, expression || [""]
710
- end
711
-
712
731
  def nested_properties!(node, space)
713
732
  err(<<MESSAGE) unless space
714
733
  Invalid CSS: a space is required between a property and its definition
@@ -720,43 +739,53 @@ MESSAGE
720
739
  block(node, :property)
721
740
  end
722
741
 
723
- def expr
724
- return unless t = term
742
+ def expr(allow_var = true)
743
+ return unless t = term(allow_var)
725
744
  res = [t, str{ss}]
726
745
 
727
- while (o = operator) && (t = term)
746
+ while (o = operator) && (t = term(allow_var))
728
747
  res << o << t << str{ss}
729
748
  end
730
749
 
731
- res
750
+ res.flatten
732
751
  end
733
752
 
734
- def term
735
- unless e = tok(NUMBER) ||
753
+ def term(allow_var)
754
+ if e = tok(NUMBER) ||
736
755
  tok(URI) ||
737
- function ||
756
+ function(allow_var) ||
738
757
  tok(STRING) ||
739
758
  tok(UNICODERANGE) ||
740
- tok(IDENT) ||
741
- tok(HEXCOLOR)
742
-
743
- return unless op = unary_operator
744
- @expected = "number or function"
745
- return [op, tok(NUMBER) || expr!(:function)]
759
+ interp_ident ||
760
+ tok(HEXCOLOR) ||
761
+ (allow_var && var_expr)
762
+ return e
746
763
  end
747
- e
764
+
765
+ return unless op = tok(/[+-]/)
766
+ @expected = "number or function"
767
+ return [op, tok(NUMBER) || function(allow_var) ||
768
+ (allow_var && var_expr) || expr!(:interpolation)]
748
769
  end
749
770
 
750
- def function
771
+ def function(allow_var)
751
772
  return unless name = tok(FUNCTION)
752
773
  if name == "expression(" || name == "calc("
753
774
  str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
754
775
  [name, str]
755
776
  else
756
- [name, str{ss}, expr, tok!(/\)/)]
777
+ [name, str{ss}, expr(allow_var), tok!(/\)/)]
757
778
  end
758
779
  end
759
780
 
781
+ def var_expr
782
+ return unless tok(/\$/)
783
+ line = @line
784
+ var = Sass::Script::Variable.new(tok!(IDENT))
785
+ var.line = line
786
+ var
787
+ end
788
+
760
789
  def interpolation
761
790
  return unless tok(INTERP_START)
762
791
  sass_script(:parse_interpolated)
@@ -789,6 +818,11 @@ MESSAGE
789
818
  res
790
819
  end
791
820
 
821
+ def interp_ident_or_var
822
+ (id = interp_ident) and return id
823
+ (var = var_expr) and return [var]
824
+ end
825
+
792
826
  def interp_name
793
827
  interp_ident NAME
794
828
  end
@@ -841,7 +875,7 @@ MESSAGE
841
875
 
842
876
  EXPR_NAMES = {
843
877
  :media_query => "media query (e.g. print, screen, print and screen)",
844
- :media_expr => "media expression (e.g. (min-device-width: 800px)))",
878
+ :media_expr => "media expression (e.g. (min-device-width: 800px))",
845
879
  :pseudo_expr => "expression (e.g. fr, 2n+1)",
846
880
  :interp_ident => "identifier",
847
881
  :interp_name => "identifier",
@@ -27,6 +27,7 @@ module Sass
27
27
  def variable; nil; end
28
28
  def script_value; nil; end
29
29
  def interpolation; nil; end
30
+ def var_expr; nil; end
30
31
  def interp_string; s = tok(STRING) and [s]; end
31
32
  def interp_ident(ident = IDENT); s = tok(ident) and [s]; end
32
33
  def use_css_import?; true; end
@@ -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
@@ -9,15 +9,29 @@ module Sass::Tree
9
9
  #
10
10
  # @see Sass::Tree
11
11
  class DirectiveNode < Node
12
- # The text of the directive, `@` and all.
12
+ # The text of the directive, `@` and all, with interpolation included.
13
13
  #
14
- # @return [String]
14
+ # @return [Array<String, Sass::Script::Node>]
15
15
  attr_accessor :value
16
16
 
17
- # @param value [String] See \{#value}
17
+ # The text of the directive after any interpolated SassScript has been resolved.
18
+ # Only set once \{Tree::Visitors::Perform} has been run.
19
+ #
20
+ # @return [String]
21
+ attr_accessor :resolved_value
22
+
23
+ # @param value [Array<String, Sass::Script::Node>] See \{#value}
18
24
  def initialize(value)
19
25
  @value = value
20
26
  super()
21
27
  end
28
+
29
+ # @param value [String] See \{#resolved_value}
30
+ # @return [DirectiveNode]
31
+ def self.resolved(value)
32
+ node = new([value])
33
+ node.resolved_value = value
34
+ node
35
+ end
22
36
  end
23
37
  end
@@ -6,9 +6,9 @@ module Sass::Tree
6
6
  #
7
7
  # @see Sass::Tree
8
8
  class MediaNode < DirectiveNode
9
- # The media query. A list of comma-separated queries (e.g. `print` or `screen`).
9
+ # The media query.
10
10
  #
11
- # @return [Array<String>]
11
+ # @return [Sass::Media::Query]
12
12
  attr_accessor :query
13
13
 
14
14
  # @see RuleNode#tabs
@@ -17,7 +17,7 @@ module Sass::Tree
17
17
  # @see RuleNode#group_end
18
18
  attr_accessor :group_end
19
19
 
20
- # @param query [Array<String>] See \{#query}
20
+ # @param query [Sass::Media::Query] See \{#query}
21
21
  def initialize(query)
22
22
  @query = query
23
23
  @tabs = 0
@@ -25,8 +25,11 @@ module Sass::Tree
25
25
  end
26
26
 
27
27
  # @see DirectiveNode#value
28
- def value
29
- "@media #{query.join(', ')}"
28
+ def value; raise NotImplementedError; end
29
+
30
+ # @see DirectiveNode#resolved_value
31
+ def resolved_value
32
+ @resolved_value ||= "@media #{query.to_css}"
30
33
  end
31
34
  end
32
35
  end
@@ -49,11 +49,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
49
49
  end
50
50
 
51
51
  def visit_comment(node)
52
- value = node.value.map do |r|
53
- next r if r.is_a?(String)
54
- "\#{#{r.to_sass(@options)}}"
55
- end.join
56
-
52
+ value = interp_to_src(node.value)
57
53
  content = if @format == :sass
58
54
  content = value.gsub(/\*\/$/, '').rstrip
59
55
  if content =~ /\A[ \t]/
@@ -91,7 +87,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
91
87
  end.gsub(/^/, spaces) + "\n"
92
88
  content
93
89
  end
94
- content.sub!(%r{^\s*(/\*)}, '/*!') if node.type == :loud
90
+ content.sub!(%r{^\s*(/\*)}, '/*!') if node.type == :loud #'
95
91
  content
96
92
  end
97
93
 
@@ -100,7 +96,8 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
100
96
  end
101
97
 
102
98
  def visit_directive(node)
103
- res = "#{tab_str}#{node.value}"
99
+ res = "#{tab_str}#{interp_to_src(node.value)}"
100
+ res.gsub!(/^@import \#\{(.*)\}([^}]*)$/, '@import \1\2');
104
101
  return res + "#{semi}\n" unless node.has_children
105
102
  res + yield + "\n"
106
103
  end
@@ -148,7 +145,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
148
145
  end
149
146
 
150
147
  def visit_media(node)
151
- "#{tab_str}@media #{node.query.join(', ')}#{yield}"
148
+ "#{tab_str}@media #{node.query.to_src(@options)}#{yield}"
152
149
  end
153
150
 
154
151
  def visit_mixindef(node)
@@ -171,7 +168,8 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
171
168
  def visit_mixin(node)
172
169
  unless node.args.empty? && node.keywords.empty?
173
170
  args = node.args.map {|a| a.to_sass(@options)}.join(", ")
174
- 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(', ')
175
173
  arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
176
174
  end
177
175
  "#{tab_str}#{@format == :sass ? '+' : '@include '}#{dasherize(node.name)}#{arglist}#{node.has_children ? yield : semi}\n"
@@ -221,6 +219,13 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
221
219
 
222
220
  private
223
221
 
222
+ def interp_to_src(interp)
223
+ interp.map do |r|
224
+ next r if r.is_a?(String)
225
+ "\#{#{r.to_sass(@options)}}"
226
+ end.join
227
+ end
228
+
224
229
  def selector_to_src(sel)
225
230
  @format == :sass ? selector_to_sass(sel) : selector_to_scss(sel)
226
231
  end
@@ -236,8 +241,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
236
241
  end
237
242
 
238
243
  def selector_to_scss(sel)
239
- sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(@options)}}"}.
240
- join.gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
244
+ interp_to_src(sel).gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
241
245
  end
242
246
 
243
247
  def semi