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
@@ -7,7 +7,6 @@ module Sass::Tree
7
7
  # @see Sass::Tree
8
8
  class RuleNode < Node
9
9
  # The character used to include the parent selector
10
- # @private
11
10
  PARENT = '&'
12
11
 
13
12
  # The CSS selector for this rule,
@@ -18,44 +17,19 @@ module Sass::Tree
18
17
  # @return [Array<String, Sass::Script::Node>]
19
18
  attr_accessor :rule
20
19
 
21
- # The CSS selectors for this rule,
22
- # parsed for commas and parent-references.
20
+ # The CSS selector for this rule,
21
+ # without any unresolved interpolation
22
+ # but with parent references still intact.
23
23
  # It's only set once {Tree::Node#perform} has been called.
24
24
  #
25
- # It's an array of arrays.
26
- # The first level of arrays comma-separated selectors;
27
- # the second represents structure within those selectors,
28
- # currently only parent-refs (represented by `:parent`).
29
- # Newlines are represented as literal `\n` characters in the strings.
30
- # For example,
31
- #
32
- # &.foo, bar, baz,
33
- # bip, &.bop, bup
34
- #
35
- # would be
36
- #
37
- # [[:parent, ".foo"], ["bar"], ["baz"],
38
- # ["\nbip"], [:parent, ".bop"], ["bup"]]
39
- #
40
- # @return [Array<Array<String, Symbol>>]
25
+ # @return [Selector::CommaSequence]
41
26
  attr_accessor :parsed_rules
42
27
 
43
- # The CSS selectors for this rule,
44
- # with all nesting and parent references resolved.
28
+ # The CSS selector for this rule,
29
+ # without any unresolved interpolation or parent references.
45
30
  # It's only set once {Tree::Node#cssize} has been called.
46
31
  #
47
- # Each element is a distinct selector, separated by commas.
48
- # Newlines are represented as literal `\n` characters in the strings.
49
- # For example,
50
- #
51
- # foo bar, baz,
52
- # bang, bip bop, blip
53
- #
54
- # would be
55
- #
56
- # ["foo bar", "baz", "\nbang", "bip bop", "blip"]
57
- #
58
- # @return [Array<String>]
32
+ # @return [Selector::CommaSequence]
59
33
  attr_accessor :resolved_rules
60
34
 
61
35
  # How deep this rule is indented
@@ -121,6 +95,7 @@ module Sass::Tree
121
95
  name.gsub(/^/, ' ' * tabs) + children_to_src(tabs, opts, :sass)
122
96
  end
123
97
 
98
+ # @see Node#to_scss
124
99
  def to_scss(tabs, opts = {})
125
100
  name = rule.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(opts)}}"}.
126
101
  join.gsub(/^[ \t]*/, ' ' * tabs)
@@ -135,6 +110,15 @@ module Sass::Tree
135
110
  res
136
111
  end
137
112
 
113
+ # Extends this Rule's selector with the given `extends`.
114
+ #
115
+ # @see Node#do_extend
116
+ def do_extend(extends)
117
+ node = dup
118
+ node.resolved_rules = resolved_rules.do_extend(extends)
119
+ node
120
+ end
121
+
138
122
  protected
139
123
 
140
124
  # Computes the CSS for the rule.
@@ -154,7 +138,9 @@ module Sass::Tree
154
138
  rule_indent = ' ' * (tabs - 1)
155
139
  per_rule_indent, total_indent = [:nested, :expanded].include?(style) ? [rule_indent, ''] : ['', rule_indent]
156
140
 
157
- total_rule = total_indent + resolved_rules.join(rule_separator).split("\n").map do |line|
141
+ total_rule = total_indent + resolved_rules.members.
142
+ map {|seq| seq.to_a.join}.
143
+ join(rule_separator).split("\n").map do |line|
158
144
  per_rule_indent + line.strip
159
145
  end.join(line_separator)
160
146
 
@@ -199,21 +185,24 @@ module Sass::Tree
199
185
  to_return
200
186
  end
201
187
 
202
- # Runs any SassScript that may be embedded in the rule,
203
- # and parses the selectors for commas.
188
+ # Runs SassScript interpolation in the selector,
189
+ # and then parses the result into a {Sass::Selector::CommaSequence}.
204
190
  #
205
191
  # @param environment [Sass::Environment] The lexical environment containing
206
192
  # variable and mixin values
207
193
  def perform!(environment)
208
- @parsed_rules = parse_selector(run_interp(@rule, environment))
194
+ @parsed_rules = Sass::SCSS::StaticParser.new(run_interp(@rule, environment)).
195
+ parse_selector(self.line, self.filename)
209
196
  super
210
197
  end
211
198
 
212
199
  # Converts nested rules into a flat list of rules.
213
200
  #
201
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
202
+ # The extensions defined for this tree
214
203
  # @param parent [RuleNode, nil] The parent node of this node,
215
204
  # or nil if the parent isn't a {RuleNode}
216
- def _cssize(parent)
205
+ def _cssize(extends, parent)
217
206
  node = super
218
207
  rules = node.children.select {|c| c.is_a?(RuleNode)}
219
208
  props = node.children.reject {|c| c.is_a?(RuleNode) || c.invisible?}
@@ -232,14 +221,28 @@ module Sass::Tree
232
221
  # Resolves parent references and nested selectors,
233
222
  # and updates the indentation based on the parent's indentation.
234
223
  #
224
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
225
+ # The extensions defined for this tree
235
226
  # @param parent [RuleNode, nil] The parent node of this node,
236
227
  # or nil if the parent isn't a {RuleNode}
237
228
  # @raise [Sass::SyntaxError] if the rule has no parents but uses `&`
238
- def cssize!(parent)
239
- self.resolved_rules = resolve_parent_refs(parent && parent.resolved_rules)
229
+ def cssize!(extends, parent)
230
+ self.resolved_rules = @parsed_rules.resolve_parent_refs(parent && parent.resolved_rules)
240
231
  super
241
232
  end
242
233
 
234
+ # Returns an error message if the given child node is invalid,
235
+ # and false otherwise.
236
+ #
237
+ # {ExtendNode}s are valid within {RuleNode}s.
238
+ #
239
+ # @param child [Tree::Node] A potential child nodecompact.
240
+ # @return [Boolean, String] Whether or not the child node is valid,
241
+ # as well as the error message to display if it is invalid
242
+ def invalid_child?(child)
243
+ super unless child.is_a?(ExtendNode)
244
+ end
245
+
243
246
  # A hash that will be associated with this rule in the CSS document
244
247
  # if the {file:SASS_REFERENCE.md#debug_info-option `:debug_info` option} is enabled.
245
248
  # This data is used by e.g. [the FireSass Firebug extension](https://addons.mozilla.org/en-US/firefox/addon/103988).
@@ -252,70 +255,16 @@ module Sass::Tree
252
255
 
253
256
  private
254
257
 
255
- def resolve_parent_refs(super_rules)
256
- if super_rules.nil?
257
- return @parsed_rules.map do |rule|
258
- if rule.include?(:parent)
259
- raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'.")
260
- end
261
-
262
- rule.join
263
- end
264
- end
265
-
266
- new_rules = []
267
- super_rules.each do |super_rule|
268
- @parsed_rules.each do |rule|
269
- new_rules << []
270
-
271
- # An initial newline of the child rule
272
- # should be moved to the beginning of the entire rule
273
- rule.first.slice!(0) if nl = (rule.first.is_a?(String) && rule.first[0] == ?\n)
274
- rule = [nl ? "\n" : "", :parent, " ", *rule] unless rule.include?(:parent)
275
-
276
- new_rules.last << rule.map do |segment|
277
- next segment unless segment == :parent
278
- super_rule
279
- end.join
280
- end
281
- end
282
- new_rules
283
- end
284
-
285
- def parse_selector(text)
286
- scanner = StringScanner.new(text)
287
- rules = [[]]
288
-
289
- while scanner.rest?
290
- rules.last << scanner.scan(/[^",&]*/)
291
- case scanner.scan(/./)
292
- when '&'; rules.last << :parent
293
- when ','
294
- scanner.scan(/\s*/)
295
- if scanner.rest?
296
- rules << []
297
- rules.last << "\n" if scanner.matched.include?("\n")
298
- end
299
- when '"'
300
- rules.last << '"' << scanner.scan(/([^"\\]|\\.)*/)
301
- # We don't want to enforce that strings are closed,
302
- # but we do want to consume quotes or trailing backslashes.
303
- rules.last << scanner.scan(/./) if scanner.rest?
304
- end
305
- end
306
-
307
- rules.map! do |l|
308
- Haml::Util.merge_adjacent_strings(l).reject {|r| r.is_a?(String) && r.empty?}
309
- end
310
-
311
- rules
312
- end
313
-
314
258
  def debug_info_rule
315
259
  node = DirectiveNode.new("@media -sass-debug-info")
316
260
  debug_info.map {|k, v| [k.to_s, v.to_s]}.sort.each do |k, v|
317
261
  rule = RuleNode.new([""])
318
- rule.resolved_rules = [[k.to_s.gsub(/[^\w-]/, "\\\\\\0")]]
262
+ rule.resolved_rules = Sass::Selector::CommaSequence.new(
263
+ [Sass::Selector::Sequence.new(
264
+ [Sass::Selector::SimpleSequence.new(
265
+ [Sass::Selector::Element.new(k.to_s.gsub(/[^\w-]/, "\\\\\\0"), nil)])
266
+ ])
267
+ ])
319
268
  prop = PropNode.new([""], "", :new)
320
269
  prop.resolved_name = "font-family"
321
270
  prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s)
@@ -16,6 +16,7 @@ module Sass
16
16
 
17
17
  protected
18
18
 
19
+ # @see Node#to_src
19
20
  def to_src(tabs, opts, fmt)
20
21
  "#{' ' * tabs}$#{dasherize(@name, opts)}: #{@expr.to_sass(opts)}#{' !default' if @guarded}#{semi fmt}\n"
21
22
  end
@@ -12,6 +12,7 @@ module Sass
12
12
 
13
13
  protected
14
14
 
15
+ # @see Node#to_src
15
16
  def to_src(tabs, opts, fmt)
16
17
  "#{' ' * tabs}@warn #{@expr.to_sass(opts)}#{semi fmt}\n"
17
18
  end
@@ -13,6 +13,7 @@ module Sass::Tree
13
13
 
14
14
  protected
15
15
 
16
+ # @see Node#to_src
16
17
  def to_src(tabs, opts, fmt)
17
18
  "#{' ' * tabs}@while #{@expr.to_sass(opts)}" + children_to_src(tabs, opts, fmt)
18
19
  end
@@ -1348,17 +1348,199 @@ SASS
1348
1348
  render("%a{:a => 'b',\n:b => 'c'}(c='d'\nd='e') bar"))
1349
1349
  end
1350
1350
 
1351
+ # Ruby Multiline
1352
+
1353
+ def test_silent_ruby_multiline
1354
+ assert_equal(<<HTML, render(<<HAML))
1355
+ bar, baz, bang
1356
+ <p>foo</p>
1357
+ HTML
1358
+ - foo = ["bar",
1359
+ "baz",
1360
+ "bang"]
1361
+ = foo.join(", ")
1362
+ %p foo
1363
+ HAML
1364
+ end
1365
+
1366
+ def test_loud_ruby_multiline
1367
+ assert_equal(<<HTML, render(<<HAML))
1368
+ bar, baz, bang
1369
+ <p>foo</p>
1370
+ <p>bar</p>
1371
+ HTML
1372
+ = ["bar",
1373
+ "baz",
1374
+ "bang"].join(", ")
1375
+ %p foo
1376
+ %p bar
1377
+ HAML
1378
+ end
1379
+
1380
+ def test_escaped_loud_ruby_multiline
1381
+ assert_equal(<<HTML, render(<<HAML))
1382
+ bar&lt;, baz, bang
1383
+ <p>foo</p>
1384
+ <p>bar</p>
1385
+ HTML
1386
+ &= ["bar<",
1387
+ "baz",
1388
+ "bang"].join(", ")
1389
+ %p foo
1390
+ %p bar
1391
+ HAML
1392
+ end
1393
+
1394
+ def test_unescaped_loud_ruby_multiline
1395
+ assert_equal(<<HTML, render(<<HAML, :escape_html => true))
1396
+ bar<, baz, bang
1397
+ <p>foo</p>
1398
+ <p>bar</p>
1399
+ HTML
1400
+ != ["bar<",
1401
+ "baz",
1402
+ "bang"].join(", ")
1403
+ %p foo
1404
+ %p bar
1405
+ HAML
1406
+ end
1407
+
1408
+ def test_flattened_loud_ruby_multiline
1409
+ assert_equal(<<HTML, render(<<HAML))
1410
+ <pre>bar&#x000A;baz&#x000A;bang</pre>
1411
+ <p>foo</p>
1412
+ <p>bar</p>
1413
+ HTML
1414
+ ~ "<pre>" + ["bar",
1415
+ "baz",
1416
+ "bang"].join("\\n") + "</pre>"
1417
+ %p foo
1418
+ %p bar
1419
+ HAML
1420
+ end
1421
+
1422
+ def test_loud_ruby_multiline_with_block
1423
+ assert_equal(<<HTML, render(<<HAML))
1424
+ farfazfang
1425
+ <p>foo</p>
1426
+ <p>bar</p>
1427
+ HTML
1428
+ = ["bar",
1429
+ "baz",
1430
+ "bang"].map do |str|
1431
+ - str.gsub("ba",
1432
+ "fa")
1433
+ %p foo
1434
+ %p bar
1435
+ HAML
1436
+ end
1437
+
1438
+ def test_silent_ruby_multiline_with_block
1439
+ assert_equal(<<HTML, render(<<HAML))
1440
+ far
1441
+ faz
1442
+ fang
1443
+ <p>foo</p>
1444
+ <p>bar</p>
1445
+ HTML
1446
+ - ["bar",
1447
+ "baz",
1448
+ "bang"].map do |str|
1449
+ = str.gsub("ba",
1450
+ "fa")
1451
+ %p foo
1452
+ %p bar
1453
+ HAML
1454
+ end
1455
+
1456
+ def test_ruby_multiline_in_tag
1457
+ assert_equal(<<HTML, render(<<HAML))
1458
+ <p>foo, bar, baz</p>
1459
+ <p>foo</p>
1460
+ <p>bar</p>
1461
+ HTML
1462
+ %p= ["foo",
1463
+ "bar",
1464
+ "baz"].join(", ")
1465
+ %p foo
1466
+ %p bar
1467
+ HAML
1468
+ end
1469
+
1470
+ def test_escaped_ruby_multiline_in_tag
1471
+ assert_equal(<<HTML, render(<<HAML))
1472
+ <p>foo&lt;, bar, baz</p>
1473
+ <p>foo</p>
1474
+ <p>bar</p>
1475
+ HTML
1476
+ %p&= ["foo<",
1477
+ "bar",
1478
+ "baz"].join(", ")
1479
+ %p foo
1480
+ %p bar
1481
+ HAML
1482
+ end
1483
+
1484
+ def test_unescaped_ruby_multiline_in_tag
1485
+ assert_equal(<<HTML, render(<<HAML, :escape_html => true))
1486
+ <p>foo<, bar, baz</p>
1487
+ <p>foo</p>
1488
+ <p>bar</p>
1489
+ HTML
1490
+ %p!= ["foo<",
1491
+ "bar",
1492
+ "baz"].join(", ")
1493
+ %p foo
1494
+ %p bar
1495
+ HAML
1496
+ end
1497
+
1498
+ def test_ruby_multiline_with_normal_multiline
1499
+ assert_equal(<<HTML, render(<<HAML))
1500
+ foobarbar, baz, bang
1501
+ <p>foo</p>
1502
+ <p>bar</p>
1503
+ HTML
1504
+ = "foo" + |
1505
+ "bar" + |
1506
+ ["bar", |
1507
+ "baz",
1508
+ "bang"].join(", ")
1509
+ %p foo
1510
+ %p bar
1511
+ HAML
1512
+ end
1513
+
1514
+ def test_ruby_multiline_after_filter
1515
+ assert_equal(<<HTML, render(<<HAML))
1516
+ foo
1517
+ bar
1518
+ bar, baz, bang
1519
+ <p>foo</p>
1520
+ <p>bar</p>
1521
+ HTML
1522
+ :plain
1523
+ foo
1524
+ bar
1525
+ = ["bar",
1526
+ "baz",
1527
+ "bang"].join(", ")
1528
+ %p foo
1529
+ %p bar
1530
+ HAML
1531
+ end
1532
+
1351
1533
  # Encodings
1352
1534
 
1353
1535
  def test_utf_8_bom
1354
- assert_equal <<CSS, render(<<SCSS)
1536
+ assert_equal <<HTML, render(<<HAML)
1355
1537
  <div class='foo'>
1356
1538
  <p>baz</p>
1357
1539
  </div>
1358
- CSS
1540
+ HTML
1359
1541
  \xEF\xBB\xBF.foo
1360
1542
  %p baz
1361
- SCSS
1543
+ HAML
1362
1544
  end
1363
1545
 
1364
1546
  unless Haml::Util.ruby1_8?