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.
- data/FAQ +138 -0
- data/MIT-LICENSE +1 -1
- data/{README → README.rdoc} +66 -3
- data/Rakefile +111 -147
- data/VERSION +1 -1
- data/bin/css2sass +0 -0
- data/bin/haml +0 -0
- data/bin/html2haml +0 -0
- data/bin/sass +0 -0
- data/init.rb +6 -1
- data/lib/haml.rb +464 -201
- data/lib/haml/buffer.rb +117 -63
- data/lib/haml/engine.rb +63 -44
- data/lib/haml/error.rb +16 -6
- data/lib/haml/exec.rb +37 -7
- data/lib/haml/filters.rb +213 -68
- data/lib/haml/helpers.rb +95 -60
- data/lib/haml/helpers/action_view_extensions.rb +1 -1
- data/lib/haml/helpers/action_view_mods.rb +54 -6
- data/lib/haml/html.rb +6 -6
- data/lib/haml/precompiler.rb +254 -133
- data/lib/haml/template.rb +3 -6
- data/lib/haml/template/patch.rb +9 -2
- data/lib/haml/template/plugin.rb +52 -23
- data/lib/sass.rb +157 -12
- data/lib/sass/constant.rb +22 -22
- data/lib/sass/constant/color.rb +13 -13
- data/lib/sass/constant/literal.rb +7 -7
- data/lib/sass/constant/number.rb +9 -9
- data/lib/sass/constant/operation.rb +4 -4
- data/lib/sass/constant/string.rb +3 -3
- data/lib/sass/css.rb +104 -31
- data/lib/sass/engine.rb +120 -39
- data/lib/sass/error.rb +1 -1
- data/lib/sass/plugin.rb +14 -3
- data/lib/sass/plugin/merb.rb +6 -2
- data/lib/sass/tree/attr_node.rb +5 -5
- data/lib/sass/tree/directive_node.rb +2 -7
- data/lib/sass/tree/node.rb +1 -12
- data/lib/sass/tree/rule_node.rb +39 -31
- data/lib/sass/tree/value_node.rb +1 -1
- data/test/benchmark.rb +67 -80
- data/test/haml/engine_test.rb +284 -84
- data/test/haml/helper_test.rb +51 -15
- data/test/haml/results/content_for_layout.xhtml +1 -2
- data/test/haml/results/eval_suppressed.xhtml +2 -4
- data/test/haml/results/filters.xhtml +44 -15
- data/test/haml/results/helpers.xhtml +2 -3
- data/test/haml/results/just_stuff.xhtml +2 -6
- data/test/haml/results/nuke_inner_whitespace.xhtml +34 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +3 -7
- data/test/haml/results/partials.xhtml +1 -0
- data/test/haml/results/tag_parsing.xhtml +1 -6
- data/test/haml/results/very_basic.xhtml +2 -4
- data/test/haml/results/whitespace_handling.xhtml +13 -21
- data/test/haml/template_test.rb +8 -15
- data/test/haml/templates/_partial.haml +1 -0
- data/test/haml/templates/filters.haml +48 -7
- data/test/haml/templates/just_stuff.haml +1 -2
- data/test/haml/templates/nuke_inner_whitespace.haml +26 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/tag_parsing.haml +0 -3
- data/test/haml/test_helper.rb +15 -0
- data/test/sass/engine_test.rb +80 -34
- data/test/sass/plugin_test.rb +1 -1
- data/test/sass/results/import.css +2 -2
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/templates/import.sass +4 -1
- data/test/sass/templates/importee.sass +4 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- metadata +65 -51
- data/lib/haml/util.rb +0 -18
- data/test/haml/runner.rb +0 -16
- data/test/profile.rb +0 -65
data/lib/sass/tree/rule_node.rb
CHANGED
@@ -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
|
-
|
21
|
-
|
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(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
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 << "#{
|
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 << "#{
|
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
|
data/lib/sass/tree/value_node.rb
CHANGED
data/test/benchmark.rb
CHANGED
@@ -1,95 +1,82 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
58
|
+
# To cache the template
|
59
|
+
@base.render 'haml/templates/standard'
|
60
|
+
@base.render 'haml/rhtml/standard'
|
77
61
|
|
78
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
data/test/haml/engine_test.rb
CHANGED
@@ -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'
|
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'
|
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
|
48
|
-
assert_equal("<
|
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
 bar
 baz</textarea>\n",
|
124
|
+
render('%textarea= "Foo\n bar\n baz"'))
|
125
|
+
|
126
|
+
assert_equal("<pre>Foo
 bar
 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 & 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 & 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&stupid' />\n",
|
183
|
+
render("%img.atlantis{:style => 'ugly&stupid'}", :escape_html => true))
|
184
|
+
assert_equal("<div class='atlantis' style='ugly&stupid'>foo</div>\n",
|
185
|
+
render(".atlantis{:style => 'ugly&stupid'} foo", :escape_html => true))
|
186
|
+
assert_equal("<p class='atlantis' style='ugly&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&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&3</p>\n", render("%p&== #{2+2}&#{2+1}", :escape_html => true))
|
206
|
+
assert_equal("<p>4&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&3\n</p>\n", render("%p\n &== #{2+2}&#{2+1}", :escape_html => true))
|
216
|
+
assert_equal("<p>\n 4&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 & 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 & 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&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
|
124
|
-
assert_equal("<p escaped='quo\"te'
|
125
|
-
assert_equal("<p escaped=\"
|
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"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
|
131
|
-
assert_equal("<p foo,bar='baz, qux'
|
132
|
-
assert_equal("<p escaped='quo\nte'
|
133
|
-
assert_equal("<p escaped='quo4te'
|
274
|
+
assert_equal("<p boom=>biddly='bar => 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'
|
189
|
-
assert_equal("<p a='b2c'
|
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
|
197
|
-
|
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(
|
215
|
-
rescue Exception =>
|
216
|
-
|
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, "#{
|
346
|
+
assert(false, "Exception not raised for\n#{key}")
|
219
347
|
end
|
220
348
|
end
|
221
349
|
end
|
222
350
|
|
223
|
-
def
|
351
|
+
def test_exception_line
|
224
352
|
render("a\nb\n!!!\n c\nd")
|
225
353
|
rescue Haml::SyntaxError => e
|
226
|
-
assert_equal(
|
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::
|
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::
|
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::
|
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 =
|
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
|