haml 1.8.2 → 2.0.0

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 (77) hide show
  1. data/FAQ +138 -0
  2. data/MIT-LICENSE +1 -1
  3. data/{README → README.rdoc} +66 -3
  4. data/Rakefile +111 -147
  5. data/VERSION +1 -1
  6. data/bin/css2sass +0 -0
  7. data/bin/haml +0 -0
  8. data/bin/html2haml +0 -0
  9. data/bin/sass +0 -0
  10. data/init.rb +6 -1
  11. data/lib/haml.rb +464 -201
  12. data/lib/haml/buffer.rb +117 -63
  13. data/lib/haml/engine.rb +63 -44
  14. data/lib/haml/error.rb +16 -6
  15. data/lib/haml/exec.rb +37 -7
  16. data/lib/haml/filters.rb +213 -68
  17. data/lib/haml/helpers.rb +95 -60
  18. data/lib/haml/helpers/action_view_extensions.rb +1 -1
  19. data/lib/haml/helpers/action_view_mods.rb +54 -6
  20. data/lib/haml/html.rb +6 -6
  21. data/lib/haml/precompiler.rb +254 -133
  22. data/lib/haml/template.rb +3 -6
  23. data/lib/haml/template/patch.rb +9 -2
  24. data/lib/haml/template/plugin.rb +52 -23
  25. data/lib/sass.rb +157 -12
  26. data/lib/sass/constant.rb +22 -22
  27. data/lib/sass/constant/color.rb +13 -13
  28. data/lib/sass/constant/literal.rb +7 -7
  29. data/lib/sass/constant/number.rb +9 -9
  30. data/lib/sass/constant/operation.rb +4 -4
  31. data/lib/sass/constant/string.rb +3 -3
  32. data/lib/sass/css.rb +104 -31
  33. data/lib/sass/engine.rb +120 -39
  34. data/lib/sass/error.rb +1 -1
  35. data/lib/sass/plugin.rb +14 -3
  36. data/lib/sass/plugin/merb.rb +6 -2
  37. data/lib/sass/tree/attr_node.rb +5 -5
  38. data/lib/sass/tree/directive_node.rb +2 -7
  39. data/lib/sass/tree/node.rb +1 -12
  40. data/lib/sass/tree/rule_node.rb +39 -31
  41. data/lib/sass/tree/value_node.rb +1 -1
  42. data/test/benchmark.rb +67 -80
  43. data/test/haml/engine_test.rb +284 -84
  44. data/test/haml/helper_test.rb +51 -15
  45. data/test/haml/results/content_for_layout.xhtml +1 -2
  46. data/test/haml/results/eval_suppressed.xhtml +2 -4
  47. data/test/haml/results/filters.xhtml +44 -15
  48. data/test/haml/results/helpers.xhtml +2 -3
  49. data/test/haml/results/just_stuff.xhtml +2 -6
  50. data/test/haml/results/nuke_inner_whitespace.xhtml +34 -0
  51. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  52. data/test/haml/results/original_engine.xhtml +3 -7
  53. data/test/haml/results/partials.xhtml +1 -0
  54. data/test/haml/results/tag_parsing.xhtml +1 -6
  55. data/test/haml/results/very_basic.xhtml +2 -4
  56. data/test/haml/results/whitespace_handling.xhtml +13 -21
  57. data/test/haml/template_test.rb +8 -15
  58. data/test/haml/templates/_partial.haml +1 -0
  59. data/test/haml/templates/filters.haml +48 -7
  60. data/test/haml/templates/just_stuff.haml +1 -2
  61. data/test/haml/templates/nuke_inner_whitespace.haml +26 -0
  62. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  63. data/test/haml/templates/tag_parsing.haml +0 -3
  64. data/test/haml/test_helper.rb +15 -0
  65. data/test/sass/engine_test.rb +80 -34
  66. data/test/sass/plugin_test.rb +1 -1
  67. data/test/sass/results/import.css +2 -2
  68. data/test/sass/results/mixins.css +95 -0
  69. data/test/sass/results/multiline.css +24 -0
  70. data/test/sass/templates/import.sass +4 -1
  71. data/test/sass/templates/importee.sass +4 -0
  72. data/test/sass/templates/mixins.sass +76 -0
  73. data/test/sass/templates/multiline.sass +20 -0
  74. metadata +65 -51
  75. data/lib/haml/util.rb +0 -18
  76. data/test/haml/runner.rb +0 -16
  77. data/test/profile.rb +0 -65
@@ -9,33 +9,50 @@ module Sass::Tree
9
9
  alias_method :rule, :value
10
10
  alias_method :rule=, :value=
11
11
 
12
+ def rules
13
+ Array(rule)
14
+ end
15
+
16
+ def add_rules(node)
17
+ self.rule = rules
18
+ self.rule += node.rules
19
+ end
20
+
12
21
  def continued?
13
22
  rule[-1] == ?,
14
23
  end
15
-
24
+
16
25
  def to_s(tabs, super_rules = nil)
17
26
  attributes = []
18
27
  sub_rules = []
19
28
 
20
- # Save this because the comma's removed by the super_rule additions
21
- was_continued = continued?
22
-
29
+ rule_split = /\s*,\s*/
30
+ rule_separator = @style == :compressed ? ',' : ', '
31
+ line_separator = [:nested, :expanded].include?(@style) ? ",\n" : rule_separator
32
+ rule_indent = ' ' * (tabs - 1)
23
33
  total_rule = if super_rules
24
- super_rules.split(/,\s*/).collect! do |s|
25
- self.rule.split(/,\s*/).collect do |r|
26
- if r.include?(PARENT)
27
- r.gsub(PARENT, s)
28
- else
29
- "#{s} #{r}"
30
- end
31
- end.join(", ")
32
- end.join(", ") + (was_continued ? ',' : '')
33
- elsif self.rule.include?(PARENT)
34
- raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'", line)
34
+ super_rules.split(",\n").map do |super_line|
35
+ super_line.strip.split(rule_split).map do |super_rule|
36
+ self.rules.map do |line|
37
+ rule_indent + line.gsub(/,$/, '').split(rule_split).map do |rule|
38
+ if rule.include?(PARENT)
39
+ rule.gsub(PARENT, super_rule)
40
+ else
41
+ "#{super_rule} #{rule}"
42
+ end
43
+ end.join(rule_separator)
44
+ end.join(line_separator)
45
+ end.join(rule_separator)
46
+ end.join(line_separator)
47
+ elsif self.rules.any? { |r| r.include?(PARENT) }
48
+ raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'.", line)
35
49
  else
36
- self.rule
50
+ per_rule_indent, total_indent = [:nested, :expanded].include?(@style) ? [rule_indent, ''] : ['', rule_indent]
51
+ total_indent + self.rules.map do |r|
52
+ per_rule_indent + r.gsub(/,$/, '').gsub(rule_split, rule_separator).rstrip
53
+ end.join(line_separator)
37
54
  end
38
-
55
+
39
56
  children.each do |child|
40
57
  if child.is_a? RuleNode
41
58
  sub_rules << child
@@ -43,38 +60,29 @@ module Sass::Tree
43
60
  attributes << child
44
61
  end
45
62
  end
46
-
63
+
47
64
  to_return = ''
48
65
  if !attributes.empty?
49
66
  old_spaces = ' ' * (tabs - 1)
50
67
  spaces = ' ' * tabs
51
68
  if @style == :compact
52
69
  attributes = attributes.map { |a| a.to_s(1) }.join(' ')
53
- to_return << "#{old_spaces}#{total_rule} { #{attributes} }\n"
70
+ to_return << "#{total_rule} { #{attributes} }\n"
54
71
  elsif @style == :compressed
55
72
  attributes = attributes.map { |a| a.to_s(1) }.join(';')
56
73
  to_return << "#{total_rule}{#{attributes}}"
57
74
  else
58
75
  attributes = attributes.map { |a| a.to_s(tabs + 1) }.join("\n")
59
76
  end_attrs = (@style == :expanded ? "\n" + old_spaces : ' ')
60
- to_return << "#{old_spaces}#{total_rule} {\n#{attributes}#{end_attrs}}\n"
77
+ to_return << "#{total_rule} {\n#{attributes}#{end_attrs}}\n"
61
78
  end
62
- elsif continued?
63
- to_return << (' ' * (tabs - 1)) + total_rule + case @style
64
- when :compressed; ''
65
- when :compact; ' '
66
- else "\n"
67
- end
68
79
  end
69
-
80
+
70
81
  tabs += 1 unless attributes.empty? || @style != :nested
71
82
  sub_rules.each do |sub|
72
- if sub.continued?
73
- check_multiline_rule(sub)
74
- end
75
-
76
83
  to_return << sub.to_s(tabs, total_rule)
77
84
  end
85
+
78
86
  to_return
79
87
  end
80
88
  end
@@ -3,7 +3,7 @@ require 'sass/tree/node'
3
3
  module Sass::Tree
4
4
  class ValueNode < Node
5
5
  attr_accessor :value
6
-
6
+
7
7
  def initialize(value, style)
8
8
  @value = value
9
9
  super(style)
@@ -1,95 +1,82 @@
1
- # There's a bizarre error where ActionController tries to load a benchmark file
2
- # and ends up finding this.
3
- # These declarations then cause it to break.
4
- # This only happens when running rcov, though, so we can avoid it.
5
- unless $0 =~ /rcov$/
6
- require File.dirname(__FILE__) + '/../lib/haml'
7
- require 'haml'
1
+ #!/usr/bin/env ruby
2
+
3
+ times = (ARGV.first || 1000).to_i
4
+
5
+ if times == 0 # Invalid parameter
6
+ puts <<END
7
+ ruby #$0 [times=1000]
8
+ Benchmark Haml against various other templating languages and Sass
9
+ on its own.
10
+ END
11
+ exit 1
12
+ end
13
+
14
+ require File.dirname(__FILE__) + '/../lib/haml'
15
+ %w[sass rubygems erb erubis markaby active_support action_controller
16
+ action_view haml/template].each(&method(:require))
17
+
18
+ begin
19
+ require 'benchwarmer'
20
+ rescue LoadError
21
+ # Since it's not as simple as gem install at the time of writing,
22
+ # we need to direct folks to the benchwarmer gem.
23
+ raise "The Haml benchmarks require the benchwarmer gem, available from http://github.com/wycats/benchwarmer"
8
24
  end
9
25
 
10
- require 'rubygems'
11
- require 'erb'
12
- require 'erubis'
13
- require 'markaby'
14
- require 'benchmark'
15
- require 'stringio'
16
- require 'open-uri'
17
-
18
- module Haml
19
- # Benchmarks Haml against ERB, Erubis, and Markaby and Sass on its own.
20
- def self.benchmark(runs = 100)
21
- template_name = 'standard'
22
- directory = File.dirname(__FILE__) + '/haml'
23
- haml_template = File.read("#{directory}/templates/#{template_name}.haml")
24
- erb_template = File.read("#{directory}/rhtml/#{template_name}.rhtml")
25
- markaby_template = File.read("#{directory}/markaby/#{template_name}.mab")
26
-
27
- puts '-'*51, "Haml and Friends: No Caching", '-'*51
28
-
29
- times = Benchmark.bmbm do |b|
30
- b.report("haml:") { runs.times { Haml::Engine.new(haml_template).render } }
31
- b.report("erb:") { runs.times { ERB.new(erb_template, nil, '-').render } }
32
- b.report("erubis:") { runs.times { Erubis::Eruby.new(erb_template).result } }
33
- b.report("mab:") { runs.times { Markaby::Template.new(markaby_template).render } }
34
- end
35
-
36
- print_result = proc do |s, n|
37
- printf "%1$*2$s %3$*4$g",
38
- "Haml/#{s}:", -13, times[0].to_a[5] / times[n].to_a[5], -17
39
- printf "%1$*2$s %3$g\n",
40
- "#{s}/Haml:", -13, times[n].to_a[5] / times[0].to_a[5]
41
- end
42
-
43
- print_result["ERB", 1]
44
- print_result["Erubis", 2]
45
- print_result["Markaby", 3]
46
-
47
- puts '', '-' * 50, 'Haml and Friends: Cached', '-' * 50
26
+ Benchmark.warmer(times) do
27
+ columns :haml, :erb, :erubis, :mab
28
+ titles :haml => "Haml", :erb => "ERB", :erubis => "Erubis", :mab => "Markaby"
48
29
 
30
+ template_name = 'standard'
31
+ directory = File.dirname(__FILE__) + '/haml'
32
+ haml_template = File.read("#{directory}/templates/#{template_name}.haml")
33
+ erb_template = File.read("#{directory}/rhtml/#{template_name}.rhtml")
34
+ markaby_template = File.read("#{directory}/markaby/#{template_name}.mab")
35
+
36
+ report "Uncached" do
37
+ haml { Haml::Engine.new(haml_template).render }
38
+ erb { ERB.new(erb_template, nil, '-').result }
39
+ erubis { Erubis::Eruby.new(erb_template).result }
40
+ mab { Markaby::Template.new(markaby_template).render }
41
+ end
42
+
43
+ report "Cached" do
49
44
  obj = Object.new
45
+
50
46
  Haml::Engine.new(haml_template).def_method(obj, :haml)
51
- erb = ERB.new(erb_template, nil, '-')
52
- obj.instance_eval("def erb; #{erb.src}; end")
53
47
  Erubis::Eruby.new(erb_template).def_method(obj, :erubis)
54
- times = Benchmark.bmbm do |b|
55
- b.report("haml:") { runs.times { obj.haml } }
56
- b.report("erb:") { runs.times { obj.erb } }
57
- b.report("erubis:") { runs.times { obj.erubis } }
58
- end
48
+ obj.instance_eval("def erb; #{ERB.new(erb_template, nil, '-').src}; end")
59
49
 
60
- print_result["ERB", 1]
61
- print_result["Erubis", 2]
62
-
63
- puts '', '-' * 50, 'Haml and ERB: Via ActionView', '-' * 50
64
-
65
- require 'active_support'
66
- require 'action_controller'
67
- require 'action_view'
68
- require 'haml/template'
50
+ haml { obj.haml }
51
+ erb { obj.erb }
52
+ erubis { obj.erubis }
53
+ end
69
54
 
55
+ report "ActionView" do
70
56
  @base = ActionView::Base.new(File.dirname(__FILE__))
71
- times = Benchmark.bmbm do |b|
72
- b.report("haml:") { runs.times { @base.render 'haml/templates/standard' } }
73
- b.report("erb:") { runs.times { @base.render 'haml/rhtml/standard' } }
74
- end
75
57
 
76
- print_result["ERB", 1]
58
+ # To cache the template
59
+ @base.render 'haml/templates/standard'
60
+ @base.render 'haml/rhtml/standard'
77
61
 
78
- puts '', '-' * 50, 'Haml and ERB: Via ActionView with deep partials', '-' * 50
62
+ haml { @base.render 'haml/templates/standard' }
63
+ erb { @base.render 'haml/rhtml/standard' }
64
+ end
79
65
 
66
+ report "ActionView with deep partials" do
80
67
  @base = ActionView::Base.new(File.dirname(__FILE__))
81
- times = Benchmark.bmbm do |b|
82
- b.report("haml:") { runs.times { @base.render 'haml/templates/action_view' } }
83
- b.report("erb:") { runs.times { @base.render 'haml/rhtml/action_view' } }
84
- end
85
-
86
- print_result["ERB", 1]
87
-
88
- puts '', '-' * 50, 'Sass', '-' * 50
89
- sass_template = File.read("#{File.dirname(__FILE__)}/sass/templates/complex.sass")
90
-
91
- Benchmark.bmbm do |b|
92
- b.report("sass:") { runs.times { Sass::Engine.new(sass_template).render } }
93
- end
68
+
69
+ # To cache the template
70
+ @base.render 'haml/templates/action_view'
71
+ @base.render 'haml/rhtml/action_view'
72
+
73
+ haml { @base.render 'haml/templates/action_view' }
74
+ erb { @base.render 'haml/rhtml/action_view' }
94
75
  end
95
76
  end
77
+
78
+ Benchmark.warmer(times) do
79
+ sass_template = File.read("#{File.dirname(__FILE__)}/sass/templates/complex.sass")
80
+
81
+ report("Sass") { Sass::Engine.new(sass_template).render }
82
+ end
@@ -1,15 +1,45 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require 'rubygems'
4
- require 'active_support'
5
- require 'action_controller'
6
- require 'action_view'
7
-
8
- require 'test/unit'
9
- require File.dirname(__FILE__) + '/../../lib/haml'
10
- require 'haml/engine'
2
+ require File.dirname(__FILE__) + '/test_helper'
11
3
 
12
4
  class EngineTest < Test::Unit::TestCase
5
+ # A map of erroneous Sass documents to the error messages they should produce.
6
+ # The error messages may be arrays;
7
+ # if so, the second element should be the line number that should be reported for the error.
8
+ # If this isn't provided, the tests will assume the line number should be the last line of the document.
9
+ EXCEPTION_MAP = {
10
+ "!!!\n a" => "Illegal nesting: nesting within a header command is illegal.",
11
+ "a\n b" => "Illegal nesting: nesting within plain text is illegal.",
12
+ "/ a\n b" => "Illegal nesting: nesting within a tag that already has content is illegal.",
13
+ "% a" => 'Invalid tag: "% a".',
14
+ "%p a\n b" => "Illegal nesting: content can't be both given on the same line as %p and nested within it.",
15
+ "%p=" => "There's no Ruby code for = to evaluate.",
16
+ "%p~" => "There's no Ruby code for ~ to evaluate.",
17
+ "~" => "There's no Ruby code for ~ to evaluate.",
18
+ "=" => "There's no Ruby code for = to evaluate.",
19
+ "%p/\n a" => "Illegal nesting: nesting within a self-closing tag is illegal.",
20
+ "%p\n\ta" => <<END.strip,
21
+ A tab character was used for indentation. Haml must be indented using two spaces.
22
+ Are you sure you have soft tabs enabled in your editor?
23
+ END
24
+ "%p\n a" => "1 space was used for indentation. Haml must be indented using two spaces.",
25
+ "%p\n a" => "3 spaces were used for indentation. Haml must be indented using two spaces.",
26
+ "%p\n a" => "4 spaces were used for indentation. Haml must be indented using two spaces.",
27
+ ":a\n b" => ['Filter "a" is not defined.', 1],
28
+ ":a= b" => 'Invalid filter name ":a= b".',
29
+ "." => "Illegal element: classes and ids must have values.",
30
+ ".#" => "Illegal element: classes and ids must have values.",
31
+ ".{} a" => "Illegal element: classes and ids must have values.",
32
+ ".= a" => "Illegal element: classes and ids must have values.",
33
+ "%p..a" => "Illegal element: classes and ids must have values.",
34
+ "%a/ b" => "Self-closing tags can't have content.",
35
+
36
+ # Regression tests
37
+ "- raise 'foo'\n\n\n\nbar" => ["foo", 1],
38
+ "= 'foo'\n-raise 'foo'" => ["foo", 2],
39
+ "\n\n\n- raise 'foo'" => ["foo", 4],
40
+ }
41
+
42
+ User = Struct.new('User', :id)
13
43
 
14
44
  def render(text, options = {}, &block)
15
45
  scope = options.delete(:scope) || Object.new
@@ -22,7 +52,7 @@ class EngineTest < Test::Unit::TestCase
22
52
  end
23
53
 
24
54
  def test_attributes_should_render_correctly
25
- assert_equal("<div class='atlantis' style='ugly'>\n</div>", render(".atlantis{:style => 'ugly'}").chomp)
55
+ assert_equal("<div class='atlantis' style='ugly'></div>", render(".atlantis{:style => 'ugly'}").chomp)
26
56
  end
27
57
 
28
58
  def test_ruby_code_should_work_inside_attributes
@@ -31,7 +61,7 @@ class EngineTest < Test::Unit::TestCase
31
61
  end
32
62
 
33
63
  def test_nil_should_render_empty_tag
34
- assert_equal("<div class='no_attributes'>\n</div>",
64
+ assert_equal("<div class='no_attributes'></div>",
35
65
  render(".no_attributes{:nil => nil}").chomp)
36
66
  end
37
67
 
@@ -44,20 +74,8 @@ class EngineTest < Test::Unit::TestCase
44
74
  assert_equal("<p>Hello</p>", render('%p Hello').chomp)
45
75
  end
46
76
 
47
- def test_long_liner_should_not_print_on_one_line
48
- assert_equal("<div>\n #{'x' * 51}\n</div>", render("%div #{'x' * 51}").chomp)
49
- end
50
-
51
- def test_non_prerendered_one_liner
52
- assert_equal("<p class='awesome'>One line</p>\n", render("%p{:class => c} One line", :locals => {:c => 'awesome'}))
53
- end
54
-
55
- def test_non_prerendered_script_one_liner
56
- assert_equal("<p class='awesome'>One line</p>\n", render("%p{:class => c}= 'One line'", :locals => {:c => 'awesome'}))
57
- end
58
-
59
- def test_non_prerendered_long_script_one_liner
60
- assert_equal("<p class='awesome'>\n #{'x' * 60}\n</p>\n", render("%p{:class => c}= 'x' * 60", :locals => {:c => 'awesome'}))
77
+ def test_one_liner_with_newline_shouldnt_be_one_line
78
+ assert_equal("<p>\n foo\n bar\n</p>", render('%p= "foo\nbar"').chomp)
61
79
  end
62
80
 
63
81
  def test_multi_render
@@ -101,36 +119,162 @@ class EngineTest < Test::Unit::TestCase
101
119
  assert_equal("<p>foo</p>\n<p>bar</p>\n<p>baz</p>\n<p>boom</p>\n", render("%p foo\r%p bar\r\n%p baz\n\r%p boom"))
102
120
  end
103
121
 
122
+ def test_textareas
123
+ assert_equal("<textarea>Foo&#x000A; bar&#x000A; baz</textarea>\n",
124
+ render('%textarea= "Foo\n bar\n baz"'))
125
+
126
+ assert_equal("<pre>Foo&#x000A; bar&#x000A; baz</pre>\n",
127
+ render('%pre= "Foo\n bar\n baz"'))
128
+
129
+ assert_equal("<textarea>#{'a' * 100}</textarea>\n",
130
+ render("%textarea #{'a' * 100}"))
131
+
132
+ assert_equal("<p>\n <textarea>Foo\n Bar\n Baz</textarea>\n</p>\n", render(<<SOURCE))
133
+ %p
134
+ %textarea
135
+ Foo
136
+ Bar
137
+ Baz
138
+ SOURCE
139
+ end
140
+
141
+ def test_boolean_attributes
142
+ assert_equal("<p bar baz='true' foo='bar'></p>\n",
143
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :html4))
144
+ assert_equal("<p bar='bar' baz='true' foo='bar'></p>\n",
145
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :xhtml))
146
+
147
+ assert_equal("<p baz='false' foo='bar'></p>\n",
148
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :html4))
149
+ assert_equal("<p baz='false' foo='bar'></p>\n",
150
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :xhtml))
151
+ end
152
+
153
+ def test_both_whitespace_nukes_work_together
154
+ assert_equal(<<RESULT, render(<<SOURCE))
155
+ <p><q>Foo
156
+ Bar</q></p>
157
+ RESULT
158
+ %p
159
+ %q><= "Foo\\nBar"
160
+ SOURCE
161
+ end
162
+
163
+ # HTML escaping tests
164
+
165
+ def test_ampersand_equals_should_escape
166
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n &= 'foo & bar'", :escape_html => false))
167
+ end
168
+
169
+ def test_ampersand_equals_inline_should_escape
170
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p&= 'foo & bar'", :escape_html => false))
171
+ end
172
+
173
+ def test_bang_equals_should_not_escape
174
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n != 'foo & bar'", :escape_html => true))
175
+ end
176
+
177
+ def test_bang_equals_inline_should_not_escape
178
+ assert_equal("<p>foo & bar</p>\n", render("%p!= 'foo & bar'", :escape_html => true))
179
+ end
180
+
181
+ def test_static_attributes_should_be_escaped
182
+ assert_equal("<img class='atlantis' style='ugly&amp;stupid' />\n",
183
+ render("%img.atlantis{:style => 'ugly&stupid'}", :escape_html => true))
184
+ assert_equal("<div class='atlantis' style='ugly&amp;stupid'>foo</div>\n",
185
+ render(".atlantis{:style => 'ugly&stupid'} foo", :escape_html => true))
186
+ assert_equal("<p class='atlantis' style='ugly&amp;stupid'>foo</p>\n",
187
+ render("%p.atlantis{:style => 'ugly&stupid'}= 'foo'", :escape_html => true))
188
+ end
189
+
190
+ def test_dynamic_attributes_should_be_escaped
191
+ assert_equal("<img alt='' src='/foo.png' />\n",
192
+ render("%img{:width => nil, :src => '/foo.png', :alt => String.new}", :escape_html => true))
193
+ assert_equal("<p alt='' src='/foo.png'>foo</p>\n",
194
+ render("%p{:width => nil, :src => '/foo.png', :alt => String.new} foo", :escape_html => true))
195
+ assert_equal("<div alt='' src='/foo.png'>foo</div>\n",
196
+ render("%div{:width => nil, :src => '/foo.png', :alt => String.new}= 'foo'", :escape_html => true))
197
+ end
198
+
199
+ def test_string_interpolation_should_be_esaped
200
+ assert_equal("<p>4&amp;3</p>\n", render("%p== #{2+2}&#{2+1}", :escape_html => true))
201
+ assert_equal("<p>4&3</p>\n", render("%p== #{2+2}&#{2+1}", :escape_html => false))
202
+ end
203
+
204
+ def test_escaped_inline_string_interpolation
205
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== #{2+2}&#{2+1}", :escape_html => true))
206
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== #{2+2}&#{2+1}", :escape_html => false))
207
+ end
208
+
209
+ def test_unescaped_inline_string_interpolation
210
+ assert_equal("<p>4&3</p>\n", render("%p!== #{2+2}&#{2+1}", :escape_html => true))
211
+ assert_equal("<p>4&3</p>\n", render("%p!== #{2+2}&#{2+1}", :escape_html => false))
212
+ end
213
+
214
+ def test_escaped_string_interpolation
215
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== #{2+2}&#{2+1}", :escape_html => true))
216
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== #{2+2}&#{2+1}", :escape_html => false))
217
+ end
218
+
219
+ def test_unescaped_string_interpolation
220
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== #{2+2}&#{2+1}", :escape_html => true))
221
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== #{2+2}&#{2+1}", :escape_html => false))
222
+ end
223
+
224
+ def test_scripts_should_respect_escape_html_option
225
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => true))
226
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => false))
227
+ end
228
+
229
+ def test_inline_scripts_should_respect_escape_html_option
230
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p= 'foo & bar'", :escape_html => true))
231
+ assert_equal("<p>foo & bar</p>\n", render("%p= 'foo & bar'", :escape_html => false))
232
+ end
233
+
234
+ def test_script_ending_in_comment_should_render_when_html_is_escaped
235
+ assert_equal("foo&amp;bar\n", render("= 'foo&bar' #comment", :escape_html => true))
236
+ end
237
+
104
238
  # Options tests
105
239
 
240
+ def test_filename_and_line
241
+ begin
242
+ render("\n\n = abc", :filename => 'test', :line => 2)
243
+ rescue Exception => e
244
+ assert_kind_of Haml::SyntaxError, e
245
+ assert_match /test:4/, e.backtrace.first
246
+ end
247
+
248
+ begin
249
+ render("\n\n= 123\n\n= nil[]", :filename => 'test', :line => 2)
250
+ rescue Exception => e
251
+ assert_kind_of NoMethodError, e
252
+ assert_match /test:6/, e.backtrace.first
253
+ end
254
+ end
255
+
106
256
  def test_stop_eval
107
257
  assert_equal("", render("= 'Hello'", :suppress_eval => true))
108
258
  assert_equal("", render("- puts 'foo'", :suppress_eval => true))
109
259
  assert_equal("<div id='foo' yes='no' />\n", render("#foo{:yes => 'no'}/", :suppress_eval => true))
110
260
  assert_equal("<div id='foo' />\n", render("#foo{:yes => 'no', :call => a_function() }/", :suppress_eval => true))
111
261
  assert_equal("<div />\n", render("%div[1]/", :suppress_eval => true))
112
-
113
- begin
114
- assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true))
115
- rescue Haml::HamlError => err
116
- caught = true
117
- assert_equal('"ruby" filter is not defined!', err.message)
118
- end
119
- assert(caught, "Rendering a ruby filter without evaluating didn't throw an error!")
262
+ assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true))
120
263
  end
121
264
 
122
265
  def test_attr_wrapper
123
- assert_equal("<p strange=*attrs*>\n</p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
124
- assert_equal("<p escaped='quo\"te'>\n</p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
125
- assert_equal("<p escaped=\"q'uo&quot;te\">\n</p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
266
+ assert_equal("<p strange=*attrs*></p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
267
+ assert_equal("<p escaped='quo\"te'></p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
268
+ assert_equal("<p escaped=\"quo'te\"></p>\n", render("%p{ :escaped => 'quo\\'te'}", :attr_wrapper => '"'))
269
+ assert_equal("<p escaped=\"q'uo&quot;te\"></p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
126
270
  assert_equal("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n", render("!!! XML", :attr_wrapper => '"'))
127
271
  end
128
272
 
129
273
  def test_attrs_parsed_correctly
130
- assert_equal("<p boom=>biddly='bar => baz'>\n</p>\n", render("%p{'boom=>biddly' => 'bar => baz'}"))
131
- assert_equal("<p foo,bar='baz, qux'>\n</p>\n", render("%p{'foo,bar' => 'baz, qux'}"))
132
- assert_equal("<p escaped='quo\nte'>\n</p>\n", render("%p{ :escaped => \"quo\\nte\"}"))
133
- assert_equal("<p escaped='quo4te'>\n</p>\n", render("%p{ :escaped => \"quo\#{2 + 2}te\"}"))
274
+ assert_equal("<p boom=>biddly='bar =&gt; baz'></p>\n", render("%p{'boom=>biddly' => 'bar => baz'}"))
275
+ assert_equal("<p foo,bar='baz, qux'></p>\n", render("%p{'foo,bar' => 'baz, qux'}"))
276
+ assert_equal("<p escaped='quo\nte'></p>\n", render("%p{ :escaped => \"quo\\nte\"}"))
277
+ assert_equal("<p escaped='quo4te'></p>\n", render("%p{ :escaped => \"quo\#{2 + 2}te\"}"))
134
278
  end
135
279
 
136
280
  def test_correct_parsing_with_brackets
@@ -143,6 +287,14 @@ class EngineTest < Test::Unit::TestCase
143
287
  foo[0] = Struct.new('Foo', :id).new
144
288
  assert_equal("<p class='struct_foo' id='struct_foo_new'>New User]</p>\n",
145
289
  render("%p[foo[0]] New User]", :locals => {:foo => foo}))
290
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_new'>New User]</p>\n",
291
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
292
+
293
+ foo[0].id = 1
294
+ assert_equal("<p class='struct_foo' id='struct_foo_1'>New User]</p>\n",
295
+ render("%p[foo[0]] New User]", :locals => {:foo => foo}))
296
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_1'>New User]</p>\n",
297
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
146
298
  end
147
299
 
148
300
  def test_empty_attrs
@@ -172,61 +324,34 @@ class EngineTest < Test::Unit::TestCase
172
324
  assert_equal("<p>Paragraph!</p>\n", render("%p= text", :locals => { :text => "Paragraph!" }))
173
325
  end
174
326
 
175
- def test_deprecated_locals_option
176
- Kernel.module_eval do
177
- def warn_with_stub(msg); end
178
- alias_method :warn_without_stub, :warn
179
- alias_method :warn, :warn_with_stub
180
- end
181
-
182
- assert_equal("<p>Paragraph!</p>\n", Haml::Engine.new("%p= text", :locals => { :text => "Paragraph!" }).render)
183
-
184
- Kernel.module_eval { alias_method :warn, :warn_without_stub }
185
- end
186
-
187
327
  def test_dynamic_attrs_shouldnt_register_as_literal_values
188
- assert_equal("<p a='b2c'>\n</p>\n", render('%p{:a => "b#{1 + 1}c"}'))
189
- assert_equal("<p a='b2c'>\n</p>\n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}"))
328
+ assert_equal("<p a='b2c'></p>\n", render('%p{:a => "b#{1 + 1}c"}'))
329
+ assert_equal("<p a='b2c'></p>\n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}"))
190
330
  end
191
331
 
192
332
  def test_dynamic_attrs_with_self_closed_tag
193
333
  assert_equal("<a b='2' />\nc\n", render("%a{'b' => 1 + 1}/\n= 'c'\n"))
194
334
  end
195
335
 
196
- def test_rec_merge
197
- hash1 = {1=>2, 3=>{5=>7, 8=>9}}
198
- hash2 = {4=>5, 3=>{5=>2, 16=>12}}
199
- hash3 = {1=>2, 4=>5, 3=>{5=>2, 8=>9, 16=>12}}
200
-
201
- hash1.rec_merge!(hash2)
202
- assert_equal(hash3, hash1)
203
- end
204
-
205
- def test_syntax_errors
206
- errs = [ "!!!\n a", "a\n b", "a\n:foo\nb", "/ a\n b",
207
- "% a", "%p a\n b", "a\n%p=\nb", "%p=\n a",
208
- "a\n%p~\nb", "a\n~\nb", "a\n~\n b", "%p~\n b", "%p/\n a",
209
- "%p\n \t%a b", "%a\n b\nc", "%a\n b\nc",
210
- ":notafilter\n This isn't\n a filter!",
211
- ".{} a", "\#{} a", ".= 'foo'", "%a/ b", "%p..class", "%p..#." ]
212
- errs.each do |err|
336
+ def test_exceptions
337
+ EXCEPTION_MAP.each do |key, value|
213
338
  begin
214
- render(err)
215
- rescue Exception => e
216
- assert(e.is_a?(Haml::Error), "#{err.dump} doesn't produce Haml::SyntaxError")
339
+ render(key)
340
+ rescue Exception => err
341
+ value = [value] unless value.is_a?(Array)
342
+
343
+ assert_equal(value.first, err.message, "Line: #{key}")
344
+ assert_equal(value[1] || key.split("\n").length, err.backtrace[0].gsub('(haml):', '').to_i, "Line: #{key}")
217
345
  else
218
- assert(false, "#{err.dump} doesn't produce an exception")
346
+ assert(false, "Exception not raised for\n#{key}")
219
347
  end
220
348
  end
221
349
  end
222
350
 
223
- def test_syntax_error
351
+ def test_exception_line
224
352
  render("a\nb\n!!!\n c\nd")
225
353
  rescue Haml::SyntaxError => e
226
- assert_equal(e.message, "Illegal Nesting: Nesting within a header command is illegal.")
227
- assert_equal("(haml):3", e.backtrace[0])
228
- rescue Exception => e
229
- assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce a Haml::SyntaxError')
354
+ assert_equal("(haml):4", e.backtrace[0])
230
355
  else
231
356
  assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce an exception')
232
357
  end
@@ -255,6 +380,11 @@ class EngineTest < Test::Unit::TestCase
255
380
  assert_equal("Unbalanced brackets.", e.message)
256
381
  end
257
382
 
383
+ def test_balanced_conditional_comments
384
+ assert_equal("<!--[if !(IE 6)|(IE 7)]> Bracket: ] <![endif]-->\n",
385
+ render("/[if !(IE 6)|(IE 7)] Bracket: ]"))
386
+ end
387
+
258
388
  def test_no_bluecloth
259
389
  Kernel.module_eval do
260
390
  def gem_original_require_with_bluecloth(file)
@@ -268,7 +398,7 @@ class EngineTest < Test::Unit::TestCase
268
398
  begin
269
399
  assert_equal("<h1>Foo</h1>\t<p>- a\n- b</p>\n",
270
400
  Haml::Engine.new(":markdown\n Foo\n ===\n - a\n - b").to_html)
271
- rescue Haml::HamlError => e
401
+ rescue Haml::Error => e
272
402
  if e.message == "Can't run Markdown filter; required 'bluecloth' or 'redcloth', but none were found"
273
403
  puts "\nCouldn't require 'bluecloth' or 'redcloth'; skipping a test."
274
404
  else
@@ -293,7 +423,7 @@ class EngineTest < Test::Unit::TestCase
293
423
 
294
424
  begin
295
425
  Haml::Engine.new(":redcloth\n _foo_").to_html
296
- rescue Haml::HamlError
426
+ rescue Haml::Error
297
427
  else
298
428
  assert(false, "No exception raised!")
299
429
  end
@@ -315,7 +445,7 @@ class EngineTest < Test::Unit::TestCase
315
445
 
316
446
  begin
317
447
  Haml::Engine.new(":markdown\n _foo_").to_html
318
- rescue Haml::HamlError
448
+ rescue Haml::Error
319
449
  else
320
450
  assert(false, "No exception raised!")
321
451
  end
@@ -325,17 +455,33 @@ class EngineTest < Test::Unit::TestCase
325
455
  end
326
456
  end
327
457
 
458
+ def test_empty_filter
459
+ assert_equal(<<END, render(':javascript'))
460
+ <script type='text/javascript'>
461
+ //<![CDATA[
462
+
463
+ //]]>
464
+ </script>
465
+ END
466
+ end
467
+
328
468
  def test_local_assigns_dont_modify_class
329
469
  assert_equal("bar\n", render("= foo", :locals => {:foo => 'bar'}))
330
470
  assert_equal(nil, defined?(foo))
331
471
  end
332
472
 
333
473
  def test_object_ref_with_nil_id
334
- user = Struct.new('User', :id).new
474
+ user = User.new
335
475
  assert_equal("<p class='struct_user' id='struct_user_new'>New User</p>\n",
336
476
  render("%p[user] New User", :locals => {:user => user}))
337
477
  end
338
478
 
479
+ def test_object_ref_before_attrs
480
+ user = User.new 42
481
+ assert_equal("<p class='struct_user' id='struct_user_42' style='width: 100px;'>New User</p>\n",
482
+ render("%p[user]{:style => 'width: 100px;'} New User", :locals => {:user => user}))
483
+ end
484
+
339
485
  def test_non_literal_attributes
340
486
  assert_equal("<p a1='foo' a2='bar' a3='baz' />\n",
341
487
  render("%p{a2, a1, :a3 => 'baz'}/",
@@ -383,4 +529,58 @@ class EngineTest < Test::Unit::TestCase
383
529
  def test_render_proc_with_binding
384
530
  assert_equal("FOO\n", Haml::Engine.new("= upcase").render_proc("foo".instance_eval{binding}).call)
385
531
  end
532
+
533
+ def test_ugly_true
534
+ assert_equal("<div id='outer'>\n<div id='inner'>\n<p>hello world</p>\n</div>\n</div>\n",
535
+ render("#outer\n #inner\n %p hello world", :ugly => true))
536
+
537
+ assert_equal("<p>#{'s' * 75}</p>\n",
538
+ render("%p #{'s' * 75}", :ugly => true))
539
+
540
+ assert_equal("<p>#{'s' * 75}</p>\n",
541
+ render("%p= 's' * 75", :ugly => true))
542
+ end
543
+
544
+ def test_xhtml_output_option
545
+ assert_equal "<p>\n <br />\n</p>\n", render("%p\n %br", :format => :xhtml)
546
+ assert_equal "<a />\n", render("%a/", :format => :xhtml)
547
+ end
548
+
549
+ def test_arbitrary_output_option
550
+ assert_raise(Haml::Error, "Invalid output format :html1") { Haml::Engine.new("%br", :format => :html1) }
551
+ end
552
+
553
+ # HTML 4.0
554
+
555
+ def test_html_has_no_self_closing_tags
556
+ assert_equal "<p>\n <br>\n</p>\n", render("%p\n %br", :format => :html4)
557
+ assert_equal "<br>\n", render("%br/", :format => :html4)
558
+ end
559
+
560
+ def test_html_renders_empty_node_with_closing_tag
561
+ assert_equal "<div class='foo'></div>\n", render(".foo", :format => :html4)
562
+ end
563
+
564
+ def test_html_ignores_explicit_self_closing_declaration
565
+ assert_equal "<a></a>\n", render("%a/", :format => :html4)
566
+ end
567
+
568
+ def test_html_ignores_xml_prolog_declaration
569
+ assert_equal "", render('!!! XML', :format => :html4)
570
+ end
571
+
572
+ def test_html_has_different_doctype
573
+ assert_equal %{<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n},
574
+ render('!!!', :format => :html4)
575
+ end
576
+
577
+ # because anything before the doctype triggers quirks mode in IE
578
+ def test_xml_prolog_and_doctype_dont_result_in_a_leading_whitespace_in_html
579
+ assert_no_match /^\s+/, render("!!! xml\n!!!", :format => :html4)
580
+ end
581
+
582
+ # HTML5
583
+ def test_html5_doctype
584
+ assert_equal %{<!DOCTYPE html>\n}, render('!!!', :format => :html5)
585
+ end
386
586
  end