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.
- data/README +17 -9
- data/Rakefile +12 -4
- data/VERSION +1 -1
- data/init.rb +1 -6
- data/lib/haml.rb +65 -7
- data/lib/haml/buffer.rb +49 -84
- data/lib/haml/engine.rb +155 -797
- data/lib/haml/error.rb +3 -33
- data/lib/haml/exec.rb +86 -65
- data/lib/haml/filters.rb +57 -27
- data/lib/haml/helpers.rb +52 -9
- data/lib/haml/helpers/action_view_mods.rb +1 -1
- data/lib/haml/html.rb +20 -5
- data/lib/haml/precompiler.rb +671 -0
- data/lib/haml/template.rb +20 -73
- data/lib/haml/template/patch.rb +51 -0
- data/lib/haml/template/plugin.rb +21 -0
- data/lib/sass.rb +78 -3
- data/lib/sass/constant.rb +45 -19
- data/lib/sass/constant.rb.rej +42 -0
- data/lib/sass/constant/string.rb +4 -0
- data/lib/sass/css.rb +162 -39
- data/lib/sass/engine.rb +38 -14
- data/lib/sass/plugin.rb +79 -44
- data/lib/sass/tree/attr_node.rb +12 -11
- data/lib/sass/tree/comment_node.rb +9 -3
- data/lib/sass/tree/directive_node.rb +51 -0
- data/lib/sass/tree/node.rb +13 -6
- data/lib/sass/tree/rule_node.rb +34 -12
- data/test/benchmark.rb +85 -52
- data/test/haml/engine_test.rb +172 -84
- data/test/haml/helper_test.rb +31 -3
- data/test/haml/html2haml_test.rb +60 -0
- data/test/haml/markaby/standard.mab +52 -0
- data/test/haml/results/eval_suppressed.xhtml +4 -1
- data/test/haml/results/helpers.xhtml +15 -4
- data/test/haml/results/just_stuff.xhtml +9 -1
- data/test/haml/results/standard.xhtml +0 -1
- data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
- data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
- data/test/haml/rhtml/action_view.rhtml +62 -0
- data/test/haml/rhtml/standard.rhtml +0 -1
- data/test/haml/template_test.rb +41 -21
- data/test/haml/templates/_av_partial_1.haml +9 -0
- data/test/haml/templates/_av_partial_2.haml +5 -0
- data/test/haml/templates/action_view.haml +47 -0
- data/test/haml/templates/eval_suppressed.haml +1 -0
- data/test/haml/templates/helpers.haml +9 -3
- data/test/haml/templates/just_stuff.haml +10 -1
- data/test/haml/templates/partials.haml +1 -1
- data/test/haml/templates/standard.haml +0 -1
- data/test/profile.rb +2 -2
- data/test/sass/engine_test.rb +113 -3
- data/test/sass/engine_test.rb.rej +18 -0
- data/test/sass/plugin_test.rb +34 -11
- data/test/sass/results/compact.css +1 -1
- data/test/sass/results/complex.css +1 -1
- data/test/sass/results/compressed.css +1 -0
- data/test/sass/results/constants.css +3 -1
- data/test/sass/results/expanded.css +2 -1
- data/test/sass/results/import.css +2 -0
- data/test/sass/results/nested.css +2 -1
- data/test/sass/templates/_partial.sass +2 -0
- data/test/sass/templates/compact.sass +2 -0
- data/test/sass/templates/complex.sass +1 -0
- data/test/sass/templates/compressed.sass +15 -0
- data/test/sass/templates/constants.sass +9 -0
- data/test/sass/templates/expanded.sass +2 -0
- data/test/sass/templates/import.sass +1 -1
- data/test/sass/templates/nested.sass +2 -0
- metadata +22 -2
data/lib/sass/tree/attr_node.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
|
11
|
-
|
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
|
data/lib/sass/tree/node.rb
CHANGED
@@ -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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
data/lib/sass/tree/rule_node.rb
CHANGED
@@ -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
|
-
|
48
|
+
if !attributes.empty?
|
49
|
+
old_spaces = ' ' * (tabs - 1)
|
50
|
+
spaces = ' ' * tabs
|
41
51
|
if @style == :compact
|
42
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
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
|
data/test/benchmark.rb
CHANGED
@@ -1,62 +1,95 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require File.dirname(__FILE__) + '/../lib/haml'
|
7
|
-
require 'haml
|
8
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
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
|
data/test/haml/engine_test.rb
CHANGED
@@ -11,21 +11,18 @@ require 'haml/engine'
|
|
11
11
|
|
12
12
|
class EngineTest < Test::Unit::TestCase
|
13
13
|
|
14
|
-
def render(text, options = {})
|
15
|
-
|
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('
|
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
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
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
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
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
|
-
|
190
|
-
|
191
|
-
def gem_original_require(file)
|
223
|
+
def gem_original_require_with_bluecloth(file)
|
192
224
|
raise LoadError if file == 'bluecloth'
|
193
|
-
|
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
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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, :
|
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
|
-
|
226
|
-
|
227
|
-
def gem_original_require(file)
|
249
|
+
def gem_original_require_with_redcloth(file)
|
228
250
|
raise LoadError if file == 'redcloth'
|
229
|
-
|
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 :
|
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
|
-
|
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
|