haml-edge 2.3.209 → 2.3.210

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/.yardopts +2 -0
  2. data/EDGE_GEM_VERSION +1 -1
  3. data/Rakefile +24 -2
  4. data/VERSION +1 -1
  5. data/lib/haml/exec.rb +11 -4
  6. data/lib/haml/filters.rb +3 -0
  7. data/lib/haml/helpers/action_view_extensions.rb +4 -2
  8. data/lib/haml/helpers/action_view_mods.rb +6 -4
  9. data/lib/haml/helpers.rb +2 -10
  10. data/lib/haml/html.rb +0 -1
  11. data/lib/haml/precompiler.rb +37 -30
  12. data/lib/haml/railtie.rb +6 -2
  13. data/lib/haml/root.rb +4 -0
  14. data/lib/haml/template.rb +2 -0
  15. data/lib/haml/util/subset_map.rb +101 -0
  16. data/lib/haml/util.rb +74 -0
  17. data/lib/haml.rb +5 -2
  18. data/lib/sass/engine.rb +36 -31
  19. data/lib/sass/files.rb +1 -1
  20. data/lib/sass/plugin/staleness_checker.rb +9 -9
  21. data/lib/sass/plugin.rb +21 -0
  22. data/lib/sass/script/color.rb +4 -3
  23. data/lib/sass/script/css_lexer.rb +11 -1
  24. data/lib/sass/script/css_parser.rb +4 -1
  25. data/lib/sass/script/funcall.rb +9 -0
  26. data/lib/sass/script/interpolation.rb +21 -0
  27. data/lib/sass/script/lexer.rb +30 -13
  28. data/lib/sass/script/node.rb +1 -1
  29. data/lib/sass/script/number.rb +4 -5
  30. data/lib/sass/script/parser.rb +13 -14
  31. data/lib/sass/script/string.rb +8 -2
  32. data/lib/sass/script/string_interpolation.rb +27 -4
  33. data/lib/sass/script.rb +1 -2
  34. data/lib/sass/scss/css_parser.rb +5 -3
  35. data/lib/sass/scss/parser.rb +146 -64
  36. data/lib/sass/scss/rx.rb +9 -1
  37. data/lib/sass/scss/sass_parser.rb +11 -0
  38. data/lib/sass/scss/script_lexer.rb +2 -0
  39. data/lib/sass/scss/static_parser.rb +48 -0
  40. data/lib/sass/scss.rb +3 -0
  41. data/lib/sass/selector/abstract_sequence.rb +40 -0
  42. data/lib/sass/selector/comma_sequence.rb +80 -0
  43. data/lib/sass/selector/sequence.rb +194 -0
  44. data/lib/sass/selector/simple.rb +107 -0
  45. data/lib/sass/selector/simple_sequence.rb +161 -0
  46. data/lib/sass/selector.rb +353 -0
  47. data/lib/sass/tree/comment_node.rb +1 -0
  48. data/lib/sass/tree/debug_node.rb +1 -0
  49. data/lib/sass/tree/directive_node.rb +1 -0
  50. data/lib/sass/tree/extend_node.rb +60 -0
  51. data/lib/sass/tree/for_node.rb +1 -0
  52. data/lib/sass/tree/if_node.rb +2 -0
  53. data/lib/sass/tree/import_node.rb +2 -0
  54. data/lib/sass/tree/mixin_def_node.rb +1 -0
  55. data/lib/sass/tree/mixin_node.rb +21 -5
  56. data/lib/sass/tree/node.rb +59 -12
  57. data/lib/sass/tree/prop_node.rb +20 -21
  58. data/lib/sass/tree/root_node.rb +8 -17
  59. data/lib/sass/tree/rule_node.rb +49 -100
  60. data/lib/sass/tree/variable_node.rb +1 -0
  61. data/lib/sass/tree/warn_node.rb +1 -0
  62. data/lib/sass/tree/while_node.rb +1 -0
  63. data/lib/sass.rb +1 -0
  64. data/test/haml/engine_test.rb +185 -3
  65. data/test/haml/helper_test.rb +25 -2
  66. data/test/haml/template_test.rb +2 -2
  67. data/test/haml/templates/helpers.haml +13 -0
  68. data/test/haml/util/subset_map_test.rb +91 -0
  69. data/test/haml/util_test.rb +25 -0
  70. data/test/sass/conversion_test.rb +23 -3
  71. data/test/sass/engine_test.rb +50 -7
  72. data/test/sass/extend_test.rb +1045 -0
  73. data/test/sass/results/complex.css +0 -1
  74. data/test/sass/results/script.css +1 -1
  75. data/test/sass/script_conversion_test.rb +16 -0
  76. data/test/sass/script_test.rb +37 -4
  77. data/test/sass/scss/css_test.rb +17 -3
  78. data/test/sass/scss/rx_test.rb +1 -1
  79. data/test/sass/scss/scss_test.rb +30 -0
  80. data/test/sass/templates/complex.sass +0 -2
  81. data/test/test_helper.rb +5 -0
  82. metadata +17 -3
@@ -1,15 +1,26 @@
1
1
  module Sass::Script
2
+ # A SassScript object representing `#{}` interpolation within a string.
3
+ #
4
+ # @see Interpolation
2
5
  class StringInterpolation < Node
6
+ # Interpolation in a string is of the form `"before #{mid} after"`,
7
+ # where `before` and `after` may include more interpolation.
8
+ #
9
+ # @param before [Node] The string before the interpolation
10
+ # @param mid [Node] The SassScript within the interpolation
11
+ # @param after [Node] The string after the interpolation
3
12
  def initialize(before, mid, after)
4
13
  @before = before
5
14
  @mid = mid
6
15
  @after = after
7
16
  end
8
17
 
18
+ # @return [String] A human-readable s-expression representation of the interpolation
9
19
  def inspect
10
20
  "(string_interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
11
21
  end
12
22
 
23
+ # @see Node#to_sass
13
24
  def to_sass(opts = {})
14
25
  # We can get rid of all of this when we remove the deprecated :equals context
15
26
  before_unquote, before_quote_char, before_str = parse_str(@before.to_sass(opts))
@@ -39,21 +50,33 @@ module Sass::Script
39
50
  res
40
51
  end
41
52
 
53
+ # Returns the three components of the interpolation, `before`, `mid`, and `after`.
54
+ #
55
+ # @return [Array<Node>]
56
+ # @see #initialize
57
+ # @see Node#children
42
58
  def children
43
59
  [@before, @mid, @after].compact
44
60
  end
45
61
 
46
62
  protected
47
63
 
64
+ # Evaluates the interpolation.
65
+ #
66
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
67
+ # @return [Sass::Script::String] The SassScript string that is the value of the interpolation
48
68
  def _perform(environment)
49
69
  res = ""
50
- res << @before.perform(environment).value
51
- val = @mid.perform(environment)
52
- res << (val.is_a?(Sass::Script::String) ? val.value : val.to_s)
70
+ before = @before.perform(environment)
71
+ res << before.value
72
+ mid = @mid.perform(environment)
73
+ res << (mid.is_a?(Sass::Script::String) ? mid.value : mid.to_s)
53
74
  res << @after.perform(environment).value
54
- Sass::Script::String.new(res, :string)
75
+ Sass::Script::String.new(res, before.type)
55
76
  end
56
77
 
78
+ private
79
+
57
80
  def parse_str(str)
58
81
  case str
59
82
  when /^unquote\((["'])(.*)\1\)$/
data/lib/sass/script.rb CHANGED
@@ -13,11 +13,9 @@ module Sass
13
13
  # This module contains code that handles the parsing and evaluation of SassScript.
14
14
  module Script
15
15
  # The regular expression used to parse variables.
16
- # @private
17
16
  MATCH = /^[!\$](#{Sass::SCSS::RX::IDENT})\s*((?:\|\|)?=|:)\s*(.+?)(!(?i:default))?$/
18
17
 
19
18
  # The regular expression used to validate variables without matching.
20
- # @private
21
19
  VALIDATE = /^[!\$]#{Sass::SCSS::RX::IDENT}$/
22
20
 
23
21
  # Parses a string of SassScript
@@ -50,6 +48,7 @@ You can use `sass-convert --in-place --from sass2 file.sass' to convert files au
50
48
  MESSAGE
51
49
  end
52
50
 
51
+ # @private
53
52
  def self.equals_warning(types, name, val, guarded, line, offset, filename)
54
53
  Haml::Util.haml_warn <<MESSAGE
55
54
  DEPRECATION WARNING:
@@ -2,15 +2,17 @@ require 'sass/script/css_parser'
2
2
 
3
3
  module Sass
4
4
  module SCSS
5
- class CssParser < Parser
5
+ # This is a subclass of {Parser} which only parses plain CSS.
6
+ # It doesn't support any Sass extensions, such as interpolation,
7
+ # parent references, nested selectors, and so forth.
8
+ # It does support all the same CSS hacks as the SCSS parser, though.
9
+ class CssParser < StaticParser
6
10
  private
7
11
 
8
- def variable; nil; end
9
12
  def parent_selector; nil; end
10
13
  def interpolation; nil; end
11
14
  def interp_string; tok(STRING); end
12
15
  def interp_ident(ident = IDENT); tok(ident); end
13
- def expected_property_separator; '":"'; end
14
16
  def use_css_import?; true; end
15
17
 
16
18
  def special_directive(name)
@@ -6,10 +6,12 @@ module Sass
6
6
  # The parser for SCSS.
7
7
  # It parses a string of code into a tree of {Sass::Tree::Node}s.
8
8
  class Parser
9
- # @param str [String] The source document to parse
10
- def initialize(str)
9
+ # @param str [String, StringScanner] The source document to parse
10
+ # @param line [Fixnum] The line on which the source string appeared,
11
+ # if it's part of another document
12
+ def initialize(str, line = 1)
11
13
  @template = str
12
- @line = 1
14
+ @line = line
13
15
  @strs = []
14
16
  end
15
17
 
@@ -18,19 +20,39 @@ module Sass
18
20
  # @return [Sass::Tree::RootNode] The root node of the document tree
19
21
  # @raise [Sass::SyntaxError] if there's a syntax error in the document
20
22
  def parse
21
- @scanner = StringScanner.new(
22
- Haml::Util.check_encoding(@template) do |msg, line|
23
- raise Sass::SyntaxError.new(msg, :line => line)
24
- end.gsub("\r", ""))
23
+ init_scanner!
25
24
  root = stylesheet
26
25
  expected("selector or at-rule") unless @scanner.eos?
27
26
  root
28
27
  end
29
28
 
29
+ # Parses an identifier with interpolation.
30
+ # Note that this won't assert that the identifier takes up the entire input string;
31
+ # it's meant to be used with `StringScanner`s as part of other parsers.
32
+ #
33
+ # @return [Array<String, Sass::Script::Node>, nil]
34
+ # The interpolated identifier, or nil if none could be parsed
35
+ def parse_interp_ident
36
+ init_scanner!
37
+ interp_ident
38
+ end
39
+
30
40
  private
31
41
 
32
42
  include Sass::SCSS::RX
33
43
 
44
+ def init_scanner!
45
+ @scanner =
46
+ if @template.is_a?(StringScanner)
47
+ @template
48
+ else
49
+ StringScanner.new(
50
+ Haml::Util.check_encoding(@template) do |msg, line|
51
+ raise Sass::SyntaxError.new(msg, :line => line)
52
+ end.gsub("\r", ""))
53
+ end
54
+ end
55
+
34
56
  def stylesheet
35
57
  node = node(Sass::Tree::RootNode.new(@scanner.string))
36
58
  block_contents(node, :stylesheet) {s(node)}
@@ -77,7 +99,7 @@ module Sass
77
99
  node << comment
78
100
  end
79
101
 
80
- DIRECTIVES = Set[:mixin, :include, :debug, :warn, :for, :while, :if, :import, :media]
102
+ DIRECTIVES = Set[:mixin, :include, :debug, :warn, :for, :while, :if, :extend, :import, :media]
81
103
 
82
104
  def directive
83
105
  return unless tok(/@/)
@@ -106,32 +128,32 @@ module Sass
106
128
 
107
129
  def special_directive(name)
108
130
  sym = name.gsub('-', '_').to_sym
109
- DIRECTIVES.include?(sym) && send(sym)
131
+ DIRECTIVES.include?(sym) && send("#{sym}_directive")
110
132
  end
111
133
 
112
- def mixin
134
+ def mixin_directive
113
135
  name = tok! IDENT
114
136
  args = sass_script(:parse_mixin_definition_arglist)
115
137
  ss
116
138
  block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive)
117
139
  end
118
140
 
119
- def include
141
+ def include_directive
120
142
  name = tok! IDENT
121
143
  args = sass_script(:parse_mixin_include_arglist)
122
144
  ss
123
145
  node(Sass::Tree::MixinNode.new(name, args))
124
146
  end
125
147
 
126
- def debug
148
+ def debug_directive
127
149
  node(Sass::Tree::DebugNode.new(sass_script(:parse)))
128
150
  end
129
151
 
130
- def warn
152
+ def warn_directive
131
153
  node(Sass::Tree::WarnNode.new(sass_script(:parse)))
132
154
  end
133
155
 
134
- def for
156
+ def for_directive
135
157
  tok!(/\$/)
136
158
  var = tok! IDENT
137
159
  ss
@@ -148,13 +170,13 @@ module Sass
148
170
  block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
149
171
  end
150
172
 
151
- def while
173
+ def while_directive
152
174
  expr = sass_script(:parse)
153
175
  ss
154
176
  block(node(Sass::Tree::WhileNode.new(expr)), :directive)
155
177
  end
156
178
 
157
- def if
179
+ def if_directive
158
180
  expr = sass_script(:parse)
159
181
  ss
160
182
  node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
@@ -173,7 +195,11 @@ module Sass
173
195
  else_block(node)
174
196
  end
175
197
 
176
- def import
198
+ def extend_directive
199
+ node(Sass::Tree::ExtendNode.new(expr!(:selector)))
200
+ end
201
+
202
+ def import_directive
177
203
  @expected = "string or url()"
178
204
  arg = tok(STRING) || tok!(URI)
179
205
  path = @scanner[1] || @scanner[2] || @scanner[3]
@@ -190,7 +216,7 @@ module Sass
190
216
 
191
217
  def use_css_import?; false; end
192
218
 
193
- def media
219
+ def media_directive
194
220
  val = str {media_query_list}.strip
195
221
  block(node(Sass::Tree::DirectiveNode.new("@media #{val}")), :directive)
196
222
  end
@@ -264,15 +290,7 @@ module Sass
264
290
  end
265
291
 
266
292
  def ruleset
267
- rules = []
268
- return unless v = selector
269
- rules.concat v
270
-
271
- while tok(/,/)
272
- rules << ',' << str {ss}
273
- rules.concat expr!(:selector)
274
- end
275
-
293
+ return unless rules = selector_sequence
276
294
  block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
277
295
  end
278
296
 
@@ -296,6 +314,7 @@ module Sass
296
314
  end
297
315
 
298
316
  def block_child(context)
317
+ return variable || directive || ruleset if context == :stylesheet
299
318
  variable || directive || declaration_or_ruleset
300
319
  end
301
320
 
@@ -340,75 +359,124 @@ module Sass
340
359
  @use_property_exception = old_use_property_exception
341
360
  end
342
361
 
362
+ def selector_sequence
363
+ if sel = tok(STATIC_SELECTOR)
364
+ return [sel]
365
+ end
366
+
367
+ rules = []
368
+ return unless v = selector
369
+ rules.concat v
370
+
371
+ while tok(/,/)
372
+ rules << ',' << str {ss}
373
+ rules.concat expr!(:selector)
374
+ end
375
+ rules
376
+ end
377
+
343
378
  def selector
379
+ return unless sel = _selector
380
+ sel.to_a
381
+ end
382
+
383
+ def _selector
344
384
  # The combinator here allows the "> E" hack
345
- return unless (comb = combinator) || (seq = simple_selector_sequence)
346
- res = [comb] + (seq || [])
385
+ return unless val = combinator || simple_selector_sequence
386
+ nl = str{ss}.include?("\n")
387
+ res = []
388
+ res << val
389
+ res << "\n" if nl
347
390
 
348
- while v = combinator
349
- res << v
350
- res.concat(simple_selector_sequence || [])
391
+ while val = combinator || simple_selector_sequence
392
+ res << val
393
+ res << "\n" if str{ss}.include?("\n")
351
394
  end
352
- res
395
+ Selector::Sequence.new(res.compact)
353
396
  end
354
397
 
355
398
  def combinator
356
- tok(PLUS) || tok(GREATER) || tok(TILDE) || str?{whitespace}
399
+ tok(PLUS) || tok(GREATER) || tok(TILDE)
357
400
  end
358
401
 
359
402
  def simple_selector_sequence
360
403
  # This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
361
404
  return expr unless e = element_name || id_selector || class_selector ||
362
- attrib || negation || pseudo || parent_selector || interpolation
405
+ attrib || negation || pseudo || parent_selector || interpolation_selector
363
406
  res = [e]
364
407
 
365
408
  # The tok(/\*/) allows the "E*" hack
366
409
  while v = element_name || id_selector || class_selector ||
367
- attrib || negation || pseudo || tok(/\*/) || interpolation
410
+ attrib || negation || pseudo || interpolation_selector ||
411
+ (tok(/\*/) && Selector::Universal.new(nil))
368
412
  res << v
369
413
  end
370
- res
414
+ expected('"{"') if tok?(/&/)
415
+
416
+ Selector::SimpleSequence.new(res)
371
417
  end
372
418
 
373
419
  def parent_selector
374
- tok(/&/)
420
+ return unless tok(/&/)
421
+ Selector::Parent.new
375
422
  end
376
423
 
377
424
  def class_selector
378
425
  return unless tok(/\./)
379
426
  @expected = "class name"
380
- ['.', expr!(:interp_ident)]
427
+ Selector::Class.new(merge(expr!(:interp_ident)))
381
428
  end
382
429
 
383
430
  def id_selector
384
431
  return unless tok(/#(?!\{)/)
385
432
  @expected = "id name"
386
- ['#', expr!(:interp_name)]
433
+ Selector::Id.new(merge(expr!(:interp_name)))
387
434
  end
388
435
 
389
436
  def element_name
390
- return unless name = interp_ident || tok(/\*/) || tok?(/\|/)
437
+ return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
391
438
  if tok(/\|/)
392
439
  @expected = "element name or *"
393
- name << "|" << (interp_ident || tok!(/\*/))
440
+ ns = name
441
+ name = interp_ident || tok!(/\*/)
442
+ end
443
+
444
+ if name == '*'
445
+ Selector::Universal.new(merge(ns))
446
+ else
447
+ Selector::Element.new(merge(name), merge(ns))
394
448
  end
395
- name
449
+ end
450
+
451
+ def interpolation_selector
452
+ return unless script = interpolation
453
+ Selector::Interpolation.new(script)
396
454
  end
397
455
 
398
456
  def attrib
399
457
  return unless tok(/\[/)
400
- res = ['[', str{ss}, attrib_name!, str{ss}]
458
+ ss
459
+ ns, name = attrib_name!
460
+ ss
401
461
 
402
- if m = tok(/=/) ||
462
+ if op = tok(/=/) ||
403
463
  tok(INCLUDES) ||
404
464
  tok(DASHMATCH) ||
405
465
  tok(PREFIXMATCH) ||
406
466
  tok(SUFFIXMATCH) ||
407
467
  tok(SUBSTRINGMATCH)
408
468
  @expected = "identifier or string"
409
- res << m << str{ss} << (tok(IDENT) || expr!(:interp_string)) << str{ss}
469
+ ss
470
+ if val = tok(IDENT)
471
+ val = [val]
472
+ else
473
+ val = expr!(:interp_string)
474
+ end
475
+ ss
410
476
  end
411
- res << tok!(/\]/)
477
+ tok(/\]/)
478
+
479
+ Selector::Attribute.new(merge(name), merge(ns), op, merge(val))
412
480
  end
413
481
 
414
482
  def attrib_name!
@@ -422,19 +490,23 @@ module Sass
422
490
  end
423
491
  else
424
492
  # *|E or |E
425
- ns = tok(/\*/) || ""
493
+ ns = [tok(/\*/) || ""]
426
494
  tok!(/\|/)
427
495
  name = expr!(:interp_ident)
428
496
  end
429
- return [ns, ("|" if ns), name]
497
+ return ns, name
430
498
  end
431
499
 
432
500
  def pseudo
433
501
  return unless s = tok(/::?/)
434
502
  @expected = "pseudoclass or pseudoelement"
435
- res = [s, expr!(:interp_ident)]
436
- return res unless tok(/\(/)
437
- res << '(' << str{ss} << expr!(:pseudo_expr) << tok!(/\)/)
503
+ name = expr!(:interp_ident)
504
+ if tok(/\(/)
505
+ ss
506
+ arg = expr!(:pseudo_expr)
507
+ tok!(/\)/)
508
+ end
509
+ Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
438
510
  end
439
511
 
440
512
  def pseudo_expr
@@ -450,10 +522,11 @@ module Sass
450
522
 
451
523
  def negation
452
524
  return unless tok(NOT)
453
- res = [":not(", str{ss}]
525
+ ss
454
526
  @expected = "selector"
455
- res << (element_name || id_selector || class_selector || attrib || expr!(:pseudo))
456
- res << tok!(/\)/)
527
+ sel = element_name || id_selector || class_selector || attrib || expr!(:pseudo)
528
+ tok!(/\)/)
529
+ Selector::Negation.new(sel)
457
530
  end
458
531
 
459
532
  def declaration
@@ -467,8 +540,8 @@ module Sass
467
540
  end
468
541
  ss
469
542
 
470
- @expected = expected_property_separator
471
- space, value = expr!(:value)
543
+ tok!(/:/)
544
+ space, value = value!
472
545
  ss
473
546
  require_block = tok?(/\{/)
474
547
 
@@ -478,16 +551,19 @@ module Sass
478
551
  nested_properties! node, space
479
552
  end
480
553
 
481
- def expected_property_separator
482
- '":" or "="'
483
- end
484
-
485
- def value
486
- return unless tok(/:/)
554
+ def value!
487
555
  space = !str {ss}.empty?
488
556
  @use_property_exception ||= space || !tok?(IDENT)
489
557
 
490
558
  return true, Sass::Script::String.new("") if tok?(/\{/)
559
+ # This is a bit of a dirty trick:
560
+ # if the value is completely static,
561
+ # we don't parse it at all, and instead return a plain old string
562
+ # containing the value.
563
+ # This results in a dramatic speed increase.
564
+ if val = tok(STATIC_VALUE)
565
+ return space, Sass::Script::String.new(val.strip)
566
+ end
491
567
  return space, sass_script(:parse)
492
568
  end
493
569
 
@@ -616,6 +692,10 @@ MESSAGE
616
692
  result
617
693
  end
618
694
 
695
+ def merge(arr)
696
+ arr && Haml::Util.merge_adjacent_strings([arr].flatten)
697
+ end
698
+
619
699
  EXPR_NAMES = {
620
700
  :media_query => "media query (e.g. print, screen, print and screen)",
621
701
  :media_expr => "media expression (e.g. (min-device-width: 800px)))",
@@ -623,11 +703,13 @@ MESSAGE
623
703
  :interp_ident => "identifier",
624
704
  :interp_name => "identifier",
625
705
  :expr => "expression (e.g. 1px, bold)",
706
+ :_selector => "selector",
707
+ :simple_selector_sequence => "selector",
626
708
  }
627
709
 
628
710
  TOK_NAMES = Haml::Util.to_hash(
629
711
  Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
630
- merge(IDENT => "identifier", /[;}]/ => '";"', /[=:]/ => '":"')
712
+ merge(IDENT => "identifier", /[;}]/ => '";"')
631
713
 
632
714
  def tok?(rx)
633
715
  @scanner.match?(rx)
data/lib/sass/scss/rx.rb CHANGED
@@ -65,7 +65,8 @@ module Sass
65
65
  NAME = /#{NMCHAR}+/
66
66
  NUM = /[0-9]+|[0-9]*.[0-9]+/
67
67
  STRING = /#{STRING1}|#{STRING2}/
68
- URL = /((?:[!#%$&*-~]|#{NONASCII}|#{ESCAPE})*)/
68
+ URLCHAR = /[#%&*-~]|#{NONASCII}|#{ESCAPE}/
69
+ URL = /(#{URLCHAR}*)/
69
70
  W = /[ \t\r\n\f]*/
70
71
 
71
72
  # This is more liberal than the spec's definition,
@@ -108,6 +109,13 @@ module Sass
108
109
  # Custom
109
110
  HEXCOLOR = /\#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?/
110
111
  INTERP_START = /#\{/
112
+
113
+ STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\"/
114
+ STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\'/
115
+ STRING_NOINTERP = /#{STRING1_NOINTERP}|#{STRING2_NOINTERP}/
116
+ STATIC_VALUE = /(#{NMCHAR}|#{STRING1_NOINTERP}|\s(?!%)|#[a-f0-9]|[,%]|\.[0-9]|\!important)+(?=[;}])/i
117
+
118
+ STATIC_SELECTOR = /(#{NMCHAR}|\s|[,>+*]|[:#.]#{NMSTART})+(?=[{])/i
111
119
  end
112
120
  end
113
121
  end
@@ -0,0 +1,11 @@
1
+ module Sass
2
+ module SCSS
3
+ # A subclass of {Parser} that parses code in Sass documents
4
+ # using some SCSS constructs.
5
+ # This is necessary because SassScript in Sass supports `!`-style variables,
6
+ # whereas in SCSS it doesn't.
7
+ class SassParser < Parser
8
+ @sass_script_parser = Sass::Script::Parser
9
+ end
10
+ end
11
+ end
@@ -4,6 +4,8 @@ module Sass
4
4
  # that makes them usable by {SCSS::Parser} to parse SassScript.
5
5
  # In particular, the lexer doesn't support `!` for a variable prefix.
6
6
  module ScriptLexer
7
+ private
8
+
7
9
  def variable
8
10
  return [:raw, "!important"] if scan(Sass::SCSS::RX::IMPORTANT)
9
11
  _variable(/(\$)(#{Sass::SCSS::RX::IDENT})/)
@@ -0,0 +1,48 @@
1
+ module Sass
2
+ module SCSS
3
+ # A parser for a static SCSS tree.
4
+ # Parses with SCSS extensions, like nested rules and parent selectors,
5
+ # but without dynamic SassScript.
6
+ # This is useful for e.g. \{#parse\_selector parsing selectors}
7
+ # after resolving the interpolation.
8
+ class StaticParser < Parser
9
+ # Parses the text as a selector.
10
+ #
11
+ # @param line [Fixnum] The line on which the selector appears.
12
+ # Used for error reporting
13
+ # @param filename [String, nil] The file in which the selector appears,
14
+ # or nil if there is no such file.
15
+ # Used for error reporting
16
+ # @return [Selector::CommaSequence] The parsed selector
17
+ # @raise [Sass::SyntaxError] if there's a syntax error in the selector
18
+ def parse_selector(line, filename)
19
+ init_scanner!
20
+ selectors = [expr!(:_selector)]
21
+ while tok(/,/)
22
+ ws = str{ss}
23
+ selectors << expr!(:_selector)
24
+ selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n")
25
+ end
26
+ expected("selector") unless @scanner.eos?
27
+ seq = Selector::CommaSequence.new(selectors)
28
+ seq.line = line
29
+ seq.filename = filename
30
+ seq
31
+ end
32
+
33
+ private
34
+
35
+ def variable; nil; end
36
+ def script_value; nil; end
37
+ def interpolation; nil; end
38
+ def interp_string; s = tok(STRING) and [s]; end
39
+ def interp_ident(ident = IDENT); s = tok(ident) and [s]; end
40
+ def use_css_import?; true; end
41
+
42
+ def special_directive(name)
43
+ return unless name == 'media' || name == 'import'
44
+ super
45
+ end
46
+ end
47
+ end
48
+ end
data/lib/sass/scss.rb CHANGED
@@ -2,6 +2,9 @@ require 'sass/scss/rx'
2
2
  require 'sass/scss/script_lexer'
3
3
  require 'sass/scss/script_parser'
4
4
  require 'sass/scss/parser'
5
+ require 'sass/scss/sass_parser'
6
+ require 'sass/scss/static_parser'
7
+ require 'sass/scss/css_parser'
5
8
 
6
9
  module Sass
7
10
  # SCSS is the CSS syntax for Sass.
@@ -0,0 +1,40 @@
1
+ module Sass
2
+ module Selector
3
+ # The abstract parent class of the various selector sequence classes.
4
+ #
5
+ # All subclasses should implement a `members` method
6
+ # that returns an array of object that respond to `#line=` and `#filename=`.
7
+ class AbstractSequence
8
+ # The line of the Sass template on which this selector was declared.
9
+ #
10
+ # @return [Fixnum]
11
+ attr_reader :line
12
+
13
+ # The name of the file in which this selector was declared.
14
+ #
15
+ # @return [String, nil]
16
+ attr_reader :filename
17
+
18
+ # Sets the line of the Sass template on which this selector was declared.
19
+ # This also sets the line for all child selectors.
20
+ #
21
+ # @param line [Fixnum]
22
+ # @return [Fixnum]
23
+ def line=(line)
24
+ members.each {|m| m.line = line}
25
+ @line = line
26
+ end
27
+
28
+ # Sets the name of the file in which this selector was declared,
29
+ # or `nil` if it was not declared in a file (e.g. on stdin).
30
+ # This also sets the filename for all child selectors.
31
+ #
32
+ # @param filename [String, nil]
33
+ # @return [String, nil]
34
+ def filename=(filename)
35
+ members.each {|m| m.filename = filename}
36
+ @filename = filename
37
+ end
38
+ end
39
+ end
40
+ end