haml 1.7.2 → 1.8.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 (71) hide show
  1. data/README +17 -9
  2. data/Rakefile +12 -4
  3. data/VERSION +1 -1
  4. data/init.rb +1 -6
  5. data/lib/haml.rb +65 -7
  6. data/lib/haml/buffer.rb +49 -84
  7. data/lib/haml/engine.rb +155 -797
  8. data/lib/haml/error.rb +3 -33
  9. data/lib/haml/exec.rb +86 -65
  10. data/lib/haml/filters.rb +57 -27
  11. data/lib/haml/helpers.rb +52 -9
  12. data/lib/haml/helpers/action_view_mods.rb +1 -1
  13. data/lib/haml/html.rb +20 -5
  14. data/lib/haml/precompiler.rb +671 -0
  15. data/lib/haml/template.rb +20 -73
  16. data/lib/haml/template/patch.rb +51 -0
  17. data/lib/haml/template/plugin.rb +21 -0
  18. data/lib/sass.rb +78 -3
  19. data/lib/sass/constant.rb +45 -19
  20. data/lib/sass/constant.rb.rej +42 -0
  21. data/lib/sass/constant/string.rb +4 -0
  22. data/lib/sass/css.rb +162 -39
  23. data/lib/sass/engine.rb +38 -14
  24. data/lib/sass/plugin.rb +79 -44
  25. data/lib/sass/tree/attr_node.rb +12 -11
  26. data/lib/sass/tree/comment_node.rb +9 -3
  27. data/lib/sass/tree/directive_node.rb +51 -0
  28. data/lib/sass/tree/node.rb +13 -6
  29. data/lib/sass/tree/rule_node.rb +34 -12
  30. data/test/benchmark.rb +85 -52
  31. data/test/haml/engine_test.rb +172 -84
  32. data/test/haml/helper_test.rb +31 -3
  33. data/test/haml/html2haml_test.rb +60 -0
  34. data/test/haml/markaby/standard.mab +52 -0
  35. data/test/haml/results/eval_suppressed.xhtml +4 -1
  36. data/test/haml/results/helpers.xhtml +15 -4
  37. data/test/haml/results/just_stuff.xhtml +9 -1
  38. data/test/haml/results/standard.xhtml +0 -1
  39. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  40. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  41. data/test/haml/rhtml/action_view.rhtml +62 -0
  42. data/test/haml/rhtml/standard.rhtml +0 -1
  43. data/test/haml/template_test.rb +41 -21
  44. data/test/haml/templates/_av_partial_1.haml +9 -0
  45. data/test/haml/templates/_av_partial_2.haml +5 -0
  46. data/test/haml/templates/action_view.haml +47 -0
  47. data/test/haml/templates/eval_suppressed.haml +1 -0
  48. data/test/haml/templates/helpers.haml +9 -3
  49. data/test/haml/templates/just_stuff.haml +10 -1
  50. data/test/haml/templates/partials.haml +1 -1
  51. data/test/haml/templates/standard.haml +0 -1
  52. data/test/profile.rb +2 -2
  53. data/test/sass/engine_test.rb +113 -3
  54. data/test/sass/engine_test.rb.rej +18 -0
  55. data/test/sass/plugin_test.rb +34 -11
  56. data/test/sass/results/compact.css +1 -1
  57. data/test/sass/results/complex.css +1 -1
  58. data/test/sass/results/compressed.css +1 -0
  59. data/test/sass/results/constants.css +3 -1
  60. data/test/sass/results/expanded.css +2 -1
  61. data/test/sass/results/import.css +2 -0
  62. data/test/sass/results/nested.css +2 -1
  63. data/test/sass/templates/_partial.sass +2 -0
  64. data/test/sass/templates/compact.sass +2 -0
  65. data/test/sass/templates/complex.sass +1 -0
  66. data/test/sass/templates/compressed.sass +15 -0
  67. data/test/sass/templates/constants.sass +9 -0
  68. data/test/sass/templates/expanded.sass +2 -0
  69. data/test/sass/templates/import.sass +1 -1
  70. data/test/sass/templates/nested.sass +2 -0
  71. metadata +22 -2
@@ -9,7 +9,7 @@ module Sass::Tree
9
9
  super(value, style)
10
10
  end
11
11
 
12
- def to_s(parent_name = nil)
12
+ def to_s(tabs, parent_name = nil)
13
13
  if value[-1] == ?;
14
14
  raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump} (This isn't CSS!)", @line)
15
15
  end
@@ -20,21 +20,22 @@ module Sass::Tree
20
20
  raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump}", @line)
21
21
  end
22
22
 
23
- join_string = @style == :compact ? ' ' : "\n"
23
+ join_string = case @style
24
+ when :compact; ' '
25
+ when :compressed; ''
26
+ else "\n"
27
+ end
28
+ spaces = ' ' * (tabs - 1)
24
29
  to_return = ''
25
30
  if !value.empty?
26
- to_return << "#{real_name}: #{value};#{join_string}"
31
+ to_return << "#{spaces}#{real_name}:#{@style == :compressed ? '' : ' '}#{value};#{join_string}"
27
32
  end
28
33
 
29
34
  children.each do |kid|
30
- if @style == :compact
31
- to_return << "#{kid.to_s(real_name)} "
32
- else
33
- to_return << "#{kid.to_s(real_name)}\n"
34
- end
35
+ to_return << "#{kid.to_s(tabs, real_name)}" << join_string
35
36
  end
36
- to_return << "\n" unless children.empty? || @style == :compact
37
- to_return[0...-1]
37
+
38
+ (@style == :compressed && parent_name) ? to_return : to_return[0...-1]
38
39
  end
39
40
 
40
41
  private
@@ -44,7 +45,7 @@ module Sass::Tree
44
45
  end
45
46
 
46
47
  def invalid_child?(child)
47
- if !child.is_a?(AttrNode)
48
+ if !child.is_a?(AttrNode) && !child.is_a?(CommentNode)
48
49
  "Illegal nesting: Only attributes may be nested beneath attributes."
49
50
  end
50
51
  end
@@ -6,9 +6,15 @@ module Sass::Tree
6
6
  super(value[2..-1].strip, style)
7
7
  end
8
8
 
9
- def to_s(parent_name = nil)
10
- join_string = @style == :compact ? ' ' : "\n * "
11
- "/* #{value}#{join_string unless children.empty?}#{children.join join_string} */"
9
+ def to_s(tabs = 0, parent_name = nil)
10
+ return if @style == :compressed
11
+
12
+ spaces = ' ' * (tabs - 1)
13
+ join_string = @style == :compact ? ' ' : "\n#{spaces} * "
14
+ str = "#{spaces}/* #{value}"
15
+ str << join_string unless children.empty?
16
+ str << "#{children.join join_string} */"
17
+ str
12
18
  end
13
19
  end
14
20
  end
@@ -0,0 +1,51 @@
1
+ require 'sass/tree/node'
2
+ require 'sass/tree/value_node'
3
+
4
+ module Sass::Tree
5
+ class DirectiveNode < ValueNode
6
+ def to_s(tabs)
7
+ if children.empty?
8
+ value + ";"
9
+ else
10
+ result = if @style == :compressed
11
+ "#{value}{"
12
+ else
13
+ "#{' ' * (tabs - 1)}#{value} {" + (@style == :compact ? ' ' : "\n")
14
+ end
15
+ was_attr = false
16
+ first = true
17
+ children.each do |child|
18
+ if child.is_a?(RuleNode) && child.continued?
19
+ check_multiline_rule(child)
20
+ continued_rule = true
21
+ end
22
+
23
+ if @style == :compact
24
+ if child.is_a?(AttrNode)
25
+ result << "#{child.to_s(first || was_attr ? 1 : tabs + 1)} "
26
+ else
27
+ if was_attr
28
+ result[-1] = "\n"
29
+ end
30
+ rendered = child.to_s(tabs + 1)
31
+ rendered.lstrip! if first
32
+ result << rendered
33
+ end
34
+ was_attr = child.is_a?(AttrNode)
35
+ first = continued_rule
36
+ elsif @style == :compressed
37
+ result << (was_attr ? ";#{child.to_s(1)}" : child.to_s(1))
38
+ was_attr = child.is_a?(AttrNode)
39
+ else
40
+ result << child.to_s(tabs + 1) + (continued_rule ? '' : "\n")
41
+ end
42
+ end
43
+ result.rstrip + if @style == :compressed
44
+ "}"
45
+ else
46
+ (@style == :expanded ? "\n" : " ") + "}\n"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -22,15 +22,22 @@ module Sass
22
22
  children.each do |child|
23
23
  if child.is_a? AttrNode
24
24
  raise SyntaxError.new('Attributes aren\'t allowed at the root of a document.', child.line)
25
+ elsif child.is_a?(RuleNode) && child.continued?
26
+ check_multiline_rule(child)
27
+ result << child.to_s(1)
28
+ else
29
+ result << "#{child.to_s(1)}" + (@style == :compressed ? '' : "\n")
25
30
  end
31
+ end
32
+ @style == :compressed ? result+"\n" : result[0...-1]
33
+ end
26
34
 
27
- begin
28
- result += "#{child.to_s(1)}\n"
29
- rescue SyntaxError => e
30
- raise e
31
- end
35
+ protected
36
+
37
+ def check_multiline_rule(rule)
38
+ unless rule.children.empty?
39
+ raise SyntaxError.new('Rules can\'t end in commas.', rule.line)
32
40
  end
33
- result[0...-1]
34
41
  end
35
42
 
36
43
  private
@@ -8,10 +8,18 @@ module Sass::Tree
8
8
 
9
9
  alias_method :rule, :value
10
10
  alias_method :rule=, :value=
11
+
12
+ def continued?
13
+ rule[-1] == ?,
14
+ end
11
15
 
12
16
  def to_s(tabs, super_rules = nil)
13
17
  attributes = []
14
18
  sub_rules = []
19
+
20
+ # Save this because the comma's removed by the super_rule additions
21
+ was_continued = continued?
22
+
15
23
  total_rule = if super_rules
16
24
  super_rules.split(/,\s*/).collect! do |s|
17
25
  self.rule.split(/,\s*/).collect do |r|
@@ -21,7 +29,7 @@ module Sass::Tree
21
29
  "#{s} #{r}"
22
30
  end
23
31
  end.join(", ")
24
- end.join(", ")
32
+ end.join(", ") + (was_continued ? ',' : '')
25
33
  elsif self.rule.include?(PARENT)
26
34
  raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'", line)
27
35
  else
@@ -37,22 +45,36 @@ module Sass::Tree
37
45
  end
38
46
 
39
47
  to_return = ''
40
- unless attributes.empty?
48
+ if !attributes.empty?
49
+ old_spaces = ' ' * (tabs - 1)
50
+ spaces = ' ' * tabs
41
51
  if @style == :compact
42
- to_return << "#{total_rule} { #{attributes.join(' ')} }\n"
52
+ attributes = attributes.map { |a| a.to_s(1) }.join(' ')
53
+ to_return << "#{old_spaces}#{total_rule} { #{attributes} }\n"
54
+ elsif @style == :compressed
55
+ attributes = attributes.map { |a| a.to_s(1) }.join(';')
56
+ to_return << "#{total_rule}{#{attributes}}"
43
57
  else
44
- spaces = (@style == :expanded ? 2 : tabs * 2)
45
- old_spaces = ' ' * (spaces - 2)
46
- spaces = ' ' * spaces
47
-
48
- attributes = attributes.join("\n").gsub("\n", "\n#{spaces}").rstrip
49
- end_attrs = (@style == :expanded ? "\n" : ' ')
50
- to_return << "#{old_spaces}#{total_rule} {\n#{spaces}#{attributes}#{end_attrs}}\n"
58
+ attributes = attributes.map { |a| a.to_s(tabs + 1) }.join("\n")
59
+ end_attrs = (@style == :expanded ? "\n" + old_spaces : ' ')
60
+ to_return << "#{old_spaces}#{total_rule} {\n#{attributes}#{end_attrs}}\n"
51
61
  end
62
+ elsif continued?
63
+ to_return << (' ' * (tabs - 1)) + total_rule + case @style
64
+ when :compressed; ''
65
+ when :compact; ' '
66
+ else "\n"
67
+ end
52
68
  end
53
69
 
54
- tabs += 1 unless attributes.empty?
55
- sub_rules.each { |sub| to_return << sub.to_s(tabs, total_rule) }
70
+ tabs += 1 unless attributes.empty? || @style != :nested
71
+ sub_rules.each do |sub|
72
+ if sub.continued?
73
+ check_multiline_rule(sub)
74
+ end
75
+
76
+ to_return << sub.to_s(tabs, total_rule)
77
+ end
56
78
  to_return
57
79
  end
58
80
  end
@@ -1,62 +1,95 @@
1
- require 'rubygems'
2
- require 'active_support'
3
- require 'action_controller'
4
- require 'action_view'
5
-
6
- require File.dirname(__FILE__) + '/../lib/haml'
7
- require 'haml/template'
8
- require 'sass/engine'
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'
8
+ end
9
9
 
10
+ require 'rubygems'
11
+ require 'erb'
12
+ require 'erubis'
13
+ require 'markaby'
10
14
  require 'benchmark'
11
15
  require 'stringio'
16
+ require 'open-uri'
12
17
 
13
18
  module Haml
14
- class Benchmarker
15
-
16
- # Creates a new benchmarker that looks for templates in the base
17
- # directory.
18
- def initialize(base = File.dirname(__FILE__))
19
- ActionView::Base.register_template_handler("haml", Haml::Template)
20
- unless base.class == ActionView::Base
21
- @base = ActionView::Base.new(base)
22
- else
23
- @base = base
24
- end
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
48
+
49
+ obj = Object.new
50
+ 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
+ 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
59
+
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'
69
+
70
+ @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' } }
25
74
  end
75
+
76
+ print_result["ERB", 1]
77
+
78
+ puts '', '-' * 50, 'Haml and ERB: Via ActionView with deep partials', '-' * 50
79
+
80
+ @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")
26
90
 
27
- # Benchmarks haml against ERb, and Sass on its own.
28
- #
29
- # Returns the results of the benchmarking as a string.
30
- #
31
- def benchmark(runs = 100)
32
- template_name = 'standard'
33
- haml_template = "haml/templates/#{template_name}"
34
- rhtml_template = "haml/rhtml/#{template_name}"
35
- sass_template = File.dirname(__FILE__) + "/sass/templates/complex.sass"
36
-
37
- old_stdout = $stdout
38
- $stdout = StringIO.new
39
-
40
- times = Benchmark.bmbm do |b|
41
- b.report("haml:") { runs.times { @base.render haml_template } }
42
- b.report("erb:") { runs.times { @base.render rhtml_template } }
43
- end
44
-
45
- #puts times[0].inspect, times[1].inspect
46
- ratio = sprintf("%g", times[0].to_a[5] / times[1].to_a[5])
47
- puts "Haml/ERB: " + ratio
48
-
49
- puts '', '-' * 50, 'Sass on its own', '-' * 50
50
-
51
- Benchmark.bmbm do |b|
52
- b.report("sass:") { runs.times { Sass::Engine.new(File.read(sass_template)).render } }
53
- end
54
-
55
- $stdout.pos = 0
56
- to_return = $stdout.read
57
- $stdout = old_stdout
58
-
59
- to_return
91
+ Benchmark.bmbm do |b|
92
+ b.report("sass:") { runs.times { Sass::Engine.new(sass_template).render } }
60
93
  end
61
94
  end
62
95
  end
@@ -11,21 +11,18 @@ require 'haml/engine'
11
11
 
12
12
  class EngineTest < Test::Unit::TestCase
13
13
 
14
- def render(text, options = {})
15
- Haml::Engine.new(text, options).to_html
14
+ def render(text, options = {}, &block)
15
+ scope = options.delete(:scope) || Object.new
16
+ locals = options.delete(:locals) || {}
17
+ Haml::Engine.new(text, options).to_html(scope, locals, &block)
16
18
  end
17
19
 
18
20
  def test_empty_render_should_remain_empty
19
21
  assert_equal('', render(''))
20
22
  end
21
23
 
22
- # This is ugly because Hashes are unordered; we don't always know the order
23
- # in which attributes will be returned.
24
- # There is probably a better way to do this.
25
24
  def test_attributes_should_render_correctly
26
25
  assert_equal("<div class='atlantis' style='ugly'>\n</div>", render(".atlantis{:style => 'ugly'}").chomp)
27
- rescue
28
- assert_equal("<div style='ugly' class='atlantis'>\n</div>", render(".atlantis{:style => 'ugly'}").chomp)
29
26
  end
30
27
 
31
28
  def test_ruby_code_should_work_inside_attributes
@@ -50,6 +47,18 @@ class EngineTest < Test::Unit::TestCase
50
47
  def test_long_liner_should_not_print_on_one_line
51
48
  assert_equal("<div>\n #{'x' * 51}\n</div>", render("%div #{'x' * 51}").chomp)
52
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'}))
61
+ end
53
62
 
54
63
  def test_multi_render
55
64
  engine = Haml::Engine.new("%strong Hi there!")
@@ -63,6 +72,11 @@ class EngineTest < Test::Unit::TestCase
63
72
  assert_equal("<p>\n Hello World\n</p>\n", render("%p\n == Hello \#{who}", :locals => {:who => 'World'}))
64
73
  end
65
74
 
75
+ def test_double_equals_in_the_middle_of_a_string
76
+ assert_equal("\"title 'Title'. \"\n",
77
+ render("== \"title '\#{\"Title\"}'. \""))
78
+ end
79
+
66
80
  def test_nil_tag_value_should_render_as_empty
67
81
  assert_equal("<p></p>\n", render("%p= nil"))
68
82
  end
@@ -71,6 +85,14 @@ class EngineTest < Test::Unit::TestCase
71
85
  assert_equal("<p></p>\n", render("%p= 'Hello' if false"))
72
86
  end
73
87
 
88
+ def test_static_attributes_with_empty_attr
89
+ assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:src => '/foo.png', :alt => ''}"))
90
+ end
91
+
92
+ def test_dynamic_attributes_with_empty_attr
93
+ assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:width => nil, :src => '/foo.png', :alt => String.new}"))
94
+ end
95
+
74
96
  # Options tests
75
97
 
76
98
  def test_stop_eval
@@ -84,7 +106,7 @@ class EngineTest < Test::Unit::TestCase
84
106
  assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true))
85
107
  rescue Haml::HamlError => err
86
108
  caught = true
87
- assert_equal('Filter "ruby" is not defined!', err.message)
109
+ assert_equal('"ruby" filter is not defined!', err.message)
88
110
  end
89
111
  assert(caught, "Rendering a ruby filter without evaluating didn't throw an error!")
90
112
  end
@@ -102,25 +124,34 @@ class EngineTest < Test::Unit::TestCase
102
124
  assert_equal("<p escaped='quo\nte'>\n</p>\n", render("%p{ :escaped => \"quo\\nte\"}"))
103
125
  assert_equal("<p escaped='quo4te'>\n</p>\n", render("%p{ :escaped => \"quo\#{2 + 2}te\"}"))
104
126
  end
127
+
128
+ def test_empty_attrs
129
+ assert_equal("<p attr=''>empty</p>\n", render("%p{ :attr => '' } empty"))
130
+ assert_equal("<p attr=''>empty</p>\n", render("%p{ :attr => x } empty", :locals => {:x => ''}))
131
+ end
132
+
133
+ def test_nil_attrs
134
+ assert_equal("<p>nil</p>\n", render("%p{ :attr => nil } nil"))
135
+ assert_equal("<p>nil</p>\n", render("%p{ :attr => x } nil", :locals => {:x => nil}))
136
+ end
105
137
 
106
138
  def test_locals
107
139
  assert_equal("<p>Paragraph!</p>\n", render("%p= text", :locals => { :text => "Paragraph!" }))
108
140
  end
109
-
110
- def test_recompile_with_new_locals
111
- template = "%p= (text == 'first time') ? text : new_text"
112
- assert_equal("<p>first time</p>\n", render(template, :locals => { :text => "first time" }))
113
- assert_equal("<p>second time</p>\n", render(template, :locals => { :text => "recompile", :new_text => "second time" }))
114
141
 
115
- # Make sure the method called will return junk unless recompiled
116
- method_name = Haml::Engine.send(:class_variable_get, '@@method_names')[template]
117
- Haml::Engine::CompiledTemplates.module_eval "def #{method_name}(stuff); @haml_stack[-1].push_text 'NOT RECOMPILED', 0; end"
142
+ def test_deprecated_locals_option
143
+ Kernel.module_eval do
144
+ def warn_with_stub(msg); end
145
+ alias_method :warn_without_stub, :warn
146
+ alias_method :warn, :warn_with_stub
147
+ end
148
+
149
+ assert_equal("<p>Paragraph!</p>\n", Haml::Engine.new("%p= text", :locals => { :text => "Paragraph!" }).render)
118
150
 
119
- assert_equal("NOT RECOMPILED\n", render(template, :locals => { :text => "first time" }))
120
- assert_equal("<p>first time</p>\n", render(template, :locals => { :text => "first time", :foo => 'bar' }))
151
+ Kernel.module_eval { alias_method :warn, :warn_without_stub }
121
152
  end
122
153
 
123
- def test_dynamc_attrs_shouldnt_register_as_literal_values
154
+ def test_dynamic_attrs_shouldnt_register_as_literal_values
124
155
  assert_equal("<p a='b2c'>\n</p>\n", render('%p{:a => "b#{1 + 1}c"}'))
125
156
  assert_equal("<p a='b2c'>\n</p>\n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}"))
126
157
  end
@@ -134,20 +165,6 @@ class EngineTest < Test::Unit::TestCase
134
165
  assert_equal(hash3, hash1)
135
166
  end
136
167
 
137
- def test_exception_type
138
- begin
139
- render("%p hi\n= undefined")
140
- rescue Exception => e
141
- assert(e.is_a?(Haml::Error))
142
- assert_equal(2, e.haml_line)
143
- assert_equal(nil, e.haml_filename)
144
- assert_equal('(haml):2', e.backtrace[0])
145
- else
146
- # Test failed... should have raised an exception
147
- assert(false)
148
- end
149
- end
150
-
151
168
  def test_syntax_errors
152
169
  errs = [ "!!!\n a", "a\n b", "a\n:foo\nb", "/ a\n b",
153
170
  "% a", "%p a\n b", "a\n%p=\nb", "%p=\n a",
@@ -159,81 +176,84 @@ class EngineTest < Test::Unit::TestCase
159
176
  begin
160
177
  render(err)
161
178
  rescue Exception => e
162
- assert(e.is_a?(Haml::Error),
163
- "#{err.dump} doesn't produce Haml::SyntaxError!")
179
+ assert(e.is_a?(Haml::Error), "#{err.dump} doesn't produce Haml::SyntaxError")
164
180
  else
165
- assert(false,
166
- "#{err.dump} doesn't produce an exception!")
181
+ assert(false, "#{err.dump} doesn't produce an exception")
167
182
  end
168
183
  end
169
184
  end
170
185
 
186
+ def test_syntax_error
187
+ render("a\nb\n!!!\n c\nd")
188
+ rescue Haml::SyntaxError => e
189
+ assert_equal(e.message, "Illegal Nesting: Nesting within a header command is illegal.")
190
+ assert_equal("(haml):3", e.backtrace[0])
191
+ rescue Exception => e
192
+ assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce a Haml::SyntaxError')
193
+ else
194
+ assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce an exception')
195
+ end
196
+
197
+ def test_exception
198
+ render("%p\n hi\n %a= undefined\n= 12")
199
+ rescue Exception => e
200
+ assert_match("(haml):3", e.backtrace[0])
201
+ else
202
+ # Test failed... should have raised an exception
203
+ assert(false)
204
+ end
205
+
171
206
  def test_compile_error
172
- begin
173
- render("a\nb\n- fee do\nc")
174
- rescue Exception => e
175
- assert_equal(3, e.haml_line)
176
- else
177
- assert(false,
178
- '"a\nb\n- fee do\nc" doesn\'t produce an exception!')
179
- end
207
+ render("a\nb\n- fee)\nc")
208
+ rescue Exception => e
209
+ assert_match(/^compile error\n\(haml\):3: syntax error/i, e.message)
210
+ else
211
+ assert(false,
212
+ '"a\nb\n- fee)\nc" doesn\'t produce an exception!')
180
213
  end
181
214
 
182
- def test_no_bluecloth
183
- old_markdown = false
184
- if defined?(Haml::Filters::Markdown)
185
- old_markdown = Haml::Filters::Markdown
186
- end
215
+ def test_unbalanced_brackets
216
+ render('== #{1 + 5} foo #{6 + 7 bar #{8 + 9}')
217
+ rescue Haml::SyntaxError => e
218
+ assert_equal("Unbalanced brackets.", e.message)
219
+ end
187
220
 
221
+ def test_no_bluecloth
188
222
  Kernel.module_eval do
189
- alias_method :haml_old_require, :gem_original_require
190
-
191
- def gem_original_require(file)
223
+ def gem_original_require_with_bluecloth(file)
192
224
  raise LoadError if file == 'bluecloth'
193
- haml_old_require(file)
194
- end
195
- end
196
-
197
- if old_markdown
198
- Haml::Filters.instance_eval do
199
- remove_const 'Markdown'
225
+ gem_original_require_without_bluecloth(file)
200
226
  end
227
+ alias_method :gem_original_require_without_bluecloth, :gem_original_require
228
+ alias_method :gem_original_require, :gem_original_require_with_bluecloth
201
229
  end
202
230
 
203
- # This is purposefully redundant, so it doesn't stop
204
- # haml/filters from being required later on.
205
- require 'haml/../haml/filters'
206
-
207
- assert_equal("<h1>Foo</h1>\t<p>- a\n- b</p>\n",
208
- Haml::Engine.new(":markdown\n Foo\n ===\n - a\n - b").to_html)
209
-
210
- Haml::Filters.instance_eval do
211
- remove_const 'Markdown'
231
+ begin
232
+ assert_equal("<h1>Foo</h1>\t<p>- a\n- b</p>\n",
233
+ Haml::Engine.new(":markdown\n Foo\n ===\n - a\n - b").to_html)
234
+ rescue Haml::HamlError => e
235
+ if e.message == "Can't run Markdown filter; required 'bluecloth' or 'redcloth', but none were found"
236
+ puts "\nCouldn't require 'bluecloth' or 'redcloth'; skipping a test."
237
+ else
238
+ raise e
239
+ end
212
240
  end
213
241
 
214
- Haml::Filters.const_set('Markdown', old_markdown) if old_markdown
215
-
216
242
  Kernel.module_eval do
217
- alias_method :gem_original_require, :haml_old_require
243
+ alias_method :gem_original_require, :gem_original_require_without_bluecloth
218
244
  end
219
-
220
- NOT_LOADED.delete 'bluecloth'
221
245
  end
222
246
 
223
247
  def test_no_redcloth
224
248
  Kernel.module_eval do
225
- alias_method :haml_old_require2, :gem_original_require
226
-
227
- def gem_original_require(file)
249
+ def gem_original_require_with_redcloth(file)
228
250
  raise LoadError if file == 'redcloth'
229
- haml_old_require2(file)
251
+ gem_original_require_without_redcloth(file)
230
252
  end
253
+ alias_method :gem_original_require_without_redcloth, :gem_original_require
254
+ alias_method :gem_original_require, :gem_original_require_with_redcloth
231
255
  end
232
256
 
233
- # This is purposefully redundant, so it doesn't stop
234
- # haml/filters from being required later on.
235
- require 'haml/../haml/../haml/filters'
236
-
237
257
  begin
238
258
  Haml::Engine.new(":redcloth\n _foo_").to_html
239
259
  rescue Haml::HamlError
@@ -242,10 +262,30 @@ class EngineTest < Test::Unit::TestCase
242
262
  end
243
263
 
244
264
  Kernel.module_eval do
245
- alias_method :gem_original_require2, :haml_old_require
265
+ alias_method :gem_original_require, :gem_original_require_without_redcloth
266
+ end
267
+ end
268
+
269
+ def test_no_redcloth_or_bluecloth
270
+ Kernel.module_eval do
271
+ def gem_original_require_with_redcloth_and_bluecloth(file)
272
+ raise LoadError if file == 'redcloth' || file == 'bluecloth'
273
+ gem_original_require_without_redcloth_and_bluecloth(file)
274
+ end
275
+ alias_method :gem_original_require_without_redcloth_and_bluecloth, :gem_original_require
276
+ alias_method :gem_original_require, :gem_original_require_with_redcloth_and_bluecloth
277
+ end
278
+
279
+ begin
280
+ Haml::Engine.new(":markdown\n _foo_").to_html
281
+ rescue Haml::HamlError
282
+ else
283
+ assert(false, "No exception raised!")
246
284
  end
247
285
 
248
- NOT_LOADED.delete 'redcloth'
286
+ Kernel.module_eval do
287
+ alias_method :gem_original_require, :gem_original_require_without_redcloth_and_bluecloth
288
+ end
249
289
  end
250
290
 
251
291
  def test_local_assigns_dont_modify_class
@@ -258,4 +298,52 @@ class EngineTest < Test::Unit::TestCase
258
298
  assert_equal("<p class='struct_user' id='struct_user_new'>New User</p>\n",
259
299
  render("%p[user] New User", :locals => {:user => user}))
260
300
  end
301
+
302
+ def test_non_literal_attributes
303
+ assert_equal("<p a1='foo' a2='bar' a3='baz' />\n",
304
+ render("%p{a2, a1, :a3 => 'baz'}/",
305
+ :locals => {:a1 => {:a1 => 'foo'}, :a2 => {:a2 => 'bar'}}))
306
+ end
307
+
308
+ def test_render_should_accept_a_binding_as_scope
309
+ string = "This is a string!"
310
+ string.instance_variable_set("@var", "Instance variable")
311
+ b = string.instance_eval do
312
+ var = "Local variable"
313
+ binding
314
+ end
315
+
316
+ assert_equal("<p>THIS IS A STRING!</p>\n<p>Instance variable</p>\n<p>Local variable</p>\n",
317
+ render("%p= upcase\n%p= @var\n%p= var", :scope => b))
318
+ end
319
+
320
+ def test_yield_should_work_with_binding
321
+ assert_equal("12\nFOO\n", render("= yield\n= upcase", :scope => "foo".instance_eval{binding}) { 12 })
322
+ end
323
+
324
+ def test_yield_should_work_with_def_method
325
+ s = "foo"
326
+ Haml::Engine.new("= yield\n= upcase").def_method(s, :render)
327
+ assert_equal("12\nFOO\n", s.render { 12 })
328
+ end
329
+
330
+ def test_def_method_with_module
331
+ Haml::Engine.new("= yield\n= upcase").def_method(String, :render_haml)
332
+ assert_equal("12\nFOO\n", "foo".render_haml { 12 })
333
+ end
334
+
335
+ def test_def_method_locals
336
+ obj = Object.new
337
+ Haml::Engine.new("%p= foo\n.bar{:baz => baz}= boom").def_method(obj, :render, :foo, :baz, :boom)
338
+ assert_equal("<p>1</p>\n<div baz='2' class='bar'>3</div>\n", obj.render(:foo => 1, :baz => 2, :boom => 3))
339
+ end
340
+
341
+ def test_render_proc_locals
342
+ proc = Haml::Engine.new("%p= foo\n.bar{:baz => baz}= boom").render_proc(Object.new, :foo, :baz, :boom)
343
+ assert_equal("<p>1</p>\n<div baz='2' class='bar'>3</div>\n", proc[:foo => 1, :baz => 2, :boom => 3])
344
+ end
345
+
346
+ def test_render_proc_with_binding
347
+ assert_equal("FOO\n", Haml::Engine.new("= upcase").render_proc("foo".instance_eval{binding}).call)
348
+ end
261
349
  end