haml 3.0.0.beta.3 → 3.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (82) hide show
  1. data/.yardopts +2 -0
  2. data/REMEMBER +4 -11
  3. data/Rakefile +24 -2
  4. data/VERSION +1 -1
  5. data/lib/haml.rb +5 -2
  6. data/lib/haml/exec.rb +11 -4
  7. data/lib/haml/filters.rb +3 -0
  8. data/lib/haml/helpers.rb +2 -10
  9. data/lib/haml/helpers/action_view_extensions.rb +4 -2
  10. data/lib/haml/helpers/action_view_mods.rb +6 -4
  11. data/lib/haml/html.rb +0 -1
  12. data/lib/haml/precompiler.rb +37 -30
  13. data/lib/haml/railtie.rb +6 -2
  14. data/lib/haml/root.rb +4 -0
  15. data/lib/haml/template.rb +2 -0
  16. data/lib/haml/util.rb +74 -0
  17. data/lib/haml/util/subset_map.rb +101 -0
  18. data/lib/sass.rb +1 -0
  19. data/lib/sass/engine.rb +36 -31
  20. data/lib/sass/files.rb +1 -1
  21. data/lib/sass/plugin.rb +21 -0
  22. data/lib/sass/plugin/staleness_checker.rb +9 -9
  23. data/lib/sass/script.rb +1 -2
  24. data/lib/sass/script/color.rb +4 -3
  25. data/lib/sass/script/css_lexer.rb +11 -1
  26. data/lib/sass/script/css_parser.rb +4 -1
  27. data/lib/sass/script/funcall.rb +9 -0
  28. data/lib/sass/script/interpolation.rb +21 -0
  29. data/lib/sass/script/lexer.rb +30 -13
  30. data/lib/sass/script/node.rb +1 -1
  31. data/lib/sass/script/number.rb +4 -5
  32. data/lib/sass/script/parser.rb +13 -14
  33. data/lib/sass/script/string.rb +8 -2
  34. data/lib/sass/script/string_interpolation.rb +27 -4
  35. data/lib/sass/scss.rb +3 -0
  36. data/lib/sass/scss/css_parser.rb +5 -3
  37. data/lib/sass/scss/parser.rb +146 -64
  38. data/lib/sass/scss/rx.rb +9 -1
  39. data/lib/sass/scss/sass_parser.rb +11 -0
  40. data/lib/sass/scss/script_lexer.rb +2 -0
  41. data/lib/sass/scss/static_parser.rb +48 -0
  42. data/lib/sass/selector.rb +353 -0
  43. data/lib/sass/selector/abstract_sequence.rb +40 -0
  44. data/lib/sass/selector/comma_sequence.rb +80 -0
  45. data/lib/sass/selector/sequence.rb +194 -0
  46. data/lib/sass/selector/simple.rb +107 -0
  47. data/lib/sass/selector/simple_sequence.rb +161 -0
  48. data/lib/sass/tree/comment_node.rb +1 -0
  49. data/lib/sass/tree/debug_node.rb +1 -0
  50. data/lib/sass/tree/directive_node.rb +1 -0
  51. data/lib/sass/tree/extend_node.rb +60 -0
  52. data/lib/sass/tree/for_node.rb +1 -0
  53. data/lib/sass/tree/if_node.rb +2 -0
  54. data/lib/sass/tree/import_node.rb +2 -0
  55. data/lib/sass/tree/mixin_def_node.rb +1 -0
  56. data/lib/sass/tree/mixin_node.rb +21 -5
  57. data/lib/sass/tree/node.rb +59 -12
  58. data/lib/sass/tree/prop_node.rb +20 -21
  59. data/lib/sass/tree/root_node.rb +8 -17
  60. data/lib/sass/tree/rule_node.rb +49 -100
  61. data/lib/sass/tree/variable_node.rb +1 -0
  62. data/lib/sass/tree/warn_node.rb +1 -0
  63. data/lib/sass/tree/while_node.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 +18 -4
@@ -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\)$/
@@ -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.
@@ -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)